diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2024-12-09 12:50:37 -0500 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2024-12-09 12:50:37 -0500 |
commit | 880e4b5069c4dfbf1ed532fc117697cde83b5566 (patch) | |
tree | 233bcbd82ff1ea8b2237232d430c665168ba009f | |
parent | f834267a75c873d143e1f7f56f73faaa5e15cf2f (diff) | |
parent | 45fa76505dd00bdd6665bae476277371fb1252a2 (diff) | |
download | lingo-archipelago-880e4b5069c4dfbf1ed532fc117697cde83b5566.tar.gz lingo-archipelago-880e4b5069c4dfbf1ed532fc117697cde83b5566.tar.bz2 lingo-archipelago-880e4b5069c4dfbf1ed532fc117697cde83b5566.zip |
Merge branch 'main' into experimental-panels
-rw-r--r-- | Archipelago/client.gd | 171 | ||||
-rw-r--r-- | Archipelago/effects.gd | 66 | ||||
-rw-r--r-- | Archipelago/extradata.gd | 84 | ||||
-rw-r--r-- | Archipelago/load.gd | 23 | ||||
-rw-r--r-- | Archipelago/multiplayer.gd | 55 | ||||
-rw-r--r-- | Archipelago/panel.gd | 11 | ||||
-rw-r--r-- | Archipelago/pause_menu.gd | 5 | ||||
-rw-r--r-- | Archipelago/settings_screen.gd | 1 | ||||
-rw-r--r-- | Archipelago/textclient.gd | 100 | ||||
-rw-r--r-- | CHANGELOG.md | 86 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | util/generate_gamedata.rb | 75 |
12 files changed, 649 insertions, 36 deletions
diff --git a/Archipelago/client.gd b/Archipelago/client.gd index 78058ca..390b14d 100644 --- a/Archipelago/client.gd +++ b/Archipelago/client.gd | |||
@@ -9,6 +9,7 @@ var SCRIPT_mypainting | |||
9 | var SCRIPT_notifier | 9 | var SCRIPT_notifier |
10 | var SCRIPT_panel | 10 | var SCRIPT_panel |
11 | var SCRIPT_pilgrimage_terminator | 11 | var SCRIPT_pilgrimage_terminator |
12 | var SCRIPT_textclient | ||
12 | var SCRIPT_uuid | 13 | var SCRIPT_uuid |
13 | 14 | ||
14 | var ap_server = "" | 15 | var ap_server = "" |
@@ -19,12 +20,12 @@ var enable_multiplayer = false | |||
19 | var track_player = false | 20 | var track_player = false |
20 | var connection_history = [] | 21 | var connection_history = [] |
21 | 22 | ||
22 | const my_version = "4.0.2" | 23 | const my_version = "5.0.1" |
23 | const ap_version = {"major": 0, "minor": 5, "build": 0, "class": "Version"} | 24 | const ap_version = {"major": 0, "minor": 5, "build": 1, "class": "Version"} |
24 | const color_items = [ | 25 | const color_items = [ |
25 | "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow" | 26 | "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow" |
26 | ] | 27 | ] |
27 | const progressive_items = { | 28 | const door_progressive_items = { |
28 | "Progressive Orange Tower": | 29 | "Progressive Orange Tower": |
29 | ["Second Floor", "Third Floor", "Fourth Floor", "Fifth Floor", "Sixth Floor", "Seventh Floor"], | 30 | ["Second Floor", "Third Floor", "Fourth Floor", "Fifth Floor", "Sixth Floor", "Seventh Floor"], |
30 | "Progressive Art Gallery": | 31 | "Progressive Art Gallery": |
@@ -36,6 +37,15 @@ const progressive_items = { | |||
36 | "Progressive Pilgrimage": | 37 | "Progressive Pilgrimage": |
37 | ["1 Sunwarp", "2 Sunwarp", "3 Sunwarp", "4 Sunwarp", "5 Sunwarp", "6 Sunwarp"] | 38 | ["1 Sunwarp", "2 Sunwarp", "3 Sunwarp", "4 Sunwarp", "5 Sunwarp", "6 Sunwarp"] |
38 | } | 39 | } |
40 | const panel_progressive_items = { | ||
41 | "Progressive Hallway Room": ["First Door", "Second Door", "Third Door", "Fourth Door"], | ||
42 | "Progressive Colorful": | ||
43 | ["White", "Black", "Red", "Yellow", "Blue", "Purple", "Orange", "Green", "Brown", "Gray"], | ||
44 | "Progressive Number Hunt": | ||
45 | ["Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Zero"], | ||
46 | "Progressive Symmetry Room": ["Near Far", "Warts Straw", "Leaf Feel"], | ||
47 | "Progressive Suits Area": ["Words Sword", "Lost", "Amen Name"] | ||
48 | } | ||
39 | 49 | ||
40 | const kTHE_END = 0 | 50 | const kTHE_END = 0 |
41 | const kTHE_MASTER = 1 | 51 | const kTHE_MASTER = 1 |
@@ -91,6 +101,7 @@ var _localdata_file = "" | |||
91 | var _death_link = false | 101 | var _death_link = false |
92 | var _victory_condition = 0 # THE END, THE MASTER, LEVEL 2 | 102 | var _victory_condition = 0 # THE END, THE MASTER, LEVEL 2 |
93 | var _door_shuffle = false | 103 | var _door_shuffle = false |
104 | var _panel_door_shuffle = false | ||
94 | var _color_shuffle = false | 105 | var _color_shuffle = false |
95 | var _panel_shuffle = 0 # none, rearrange | 106 | var _panel_shuffle = 0 # none, rearrange |
96 | var _painting_shuffle = false | 107 | var _painting_shuffle = false |
@@ -105,6 +116,7 @@ var _pilgrimage_allows_roof_access = false | |||
105 | var _pilgrimage_allows_paintings = false | 116 | var _pilgrimage_allows_paintings = false |
106 | var _sunwarp_shuffle = false | 117 | var _sunwarp_shuffle = false |
107 | var _sunwarp_mapping = [] | 118 | var _sunwarp_mapping = [] |
119 | var _speed_boost_mode = false | ||
108 | var _slot_seed = 0 | 120 | var _slot_seed = 0 |
109 | 121 | ||
110 | var _map_loaded = false | 122 | var _map_loaded = false |
@@ -118,8 +130,11 @@ var _puzzle_skips = 0 | |||
118 | var _cached_slowness = 0 | 130 | var _cached_slowness = 0 |
119 | var _cached_iceland = 0 | 131 | var _cached_iceland = 0 |
120 | var _cached_atbash = 0 | 132 | var _cached_atbash = 0 |
133 | var _cached_speed_boosts = 0 | ||
121 | var _geronimo_skip = false | 134 | var _geronimo_skip = false |
122 | var _checked_paintings = [] | 135 | var _checked_paintings = [] |
136 | var _hints_key = "" | ||
137 | var _hinted_locations = [] | ||
123 | 138 | ||
124 | signal could_not_connect | 139 | signal could_not_connect |
125 | signal connect_status | 140 | signal connect_status |
@@ -277,9 +292,15 @@ func _on_data(): | |||
277 | _color_shuffle = _slot_data["shuffle_colors"] | 292 | _color_shuffle = _slot_data["shuffle_colors"] |
278 | 293 | ||
279 | if _slot_data.has("shuffle_doors"): | 294 | if _slot_data.has("shuffle_doors"): |
280 | _door_shuffle = (_slot_data["shuffle_doors"] > 0) | 295 | if _slot_data.has("group_doors"): |
296 | _door_shuffle = (_slot_data["shuffle_doors"] == 2) | ||
297 | _panel_door_shuffle = (_slot_data["shuffle_doors"] == 1) | ||
298 | else: | ||
299 | _door_shuffle = (_slot_data["shuffle_doors"] > 0) | ||
300 | _panel_door_shuffle = false | ||
281 | else: | 301 | else: |
282 | _door_shuffle = false | 302 | _door_shuffle = false |
303 | _panel_door_shuffle = false | ||
283 | 304 | ||
284 | if _slot_data.has("shuffle_paintings"): | 305 | if _slot_data.has("shuffle_paintings"): |
285 | _painting_shuffle = _slot_data["shuffle_paintings"] | 306 | _painting_shuffle = _slot_data["shuffle_paintings"] |
@@ -331,6 +352,10 @@ func _on_data(): | |||
331 | _sunwarp_shuffle = false | 352 | _sunwarp_shuffle = false |
332 | if _slot_data.has("sunwarp_permutation"): | 353 | if _slot_data.has("sunwarp_permutation"): |
333 | _sunwarp_mapping = _slot_data["sunwarp_permutation"] | 354 | _sunwarp_mapping = _slot_data["sunwarp_permutation"] |
355 | if _slot_data.has("speed_boost_mode"): | ||
356 | _speed_boost_mode = _slot_data["speed_boost_mode"] | ||
357 | else: | ||
358 | _speed_boost_mode = false | ||
334 | 359 | ||
335 | if ( | 360 | if ( |
336 | _location_classification_bit != kCLASSIFICATION_LOCAL_INSANITY | 361 | _location_classification_bit != kCLASSIFICATION_LOCAL_INSANITY |
@@ -349,6 +374,7 @@ func _on_data(): | |||
349 | _cached_slowness = 0 | 374 | _cached_slowness = 0 |
350 | _cached_iceland = 0 | 375 | _cached_iceland = 0 |
351 | _cached_atbash = 0 | 376 | _cached_atbash = 0 |
377 | _cached_speed_boosts = 0 | ||
352 | _geronimo_skip = false | 378 | _geronimo_skip = false |
353 | 379 | ||
354 | _localdata_file = "user://archipelago_data/%s_%d" % [_seed, _slot] | 380 | _localdata_file = "user://archipelago_data/%s_%d" % [_seed, _slot] |
@@ -380,6 +406,9 @@ func _on_data(): | |||
380 | if localdata.size() > 5: | 406 | if localdata.size() > 5: |
381 | _geronimo_skip = localdata[5] | 407 | _geronimo_skip = localdata[5] |
382 | 408 | ||
409 | if localdata.size() > 6: | ||
410 | _cached_speed_boosts = localdata[6] | ||
411 | |||
383 | requestSync() | 412 | requestSync() |
384 | 413 | ||
385 | sendMessage( | 414 | sendMessage( |
@@ -394,6 +423,11 @@ func _on_data(): | |||
394 | ] | 423 | ] |
395 | ) | 424 | ) |
396 | 425 | ||
426 | _hints_key = "_read_hints_%d_%d" % [_team, _slot] | ||
427 | sendMessage( | ||
428 | [{"cmd": "SetNotify", "keys": [_hints_key]}, {"cmd": "Get", "keys": [_hints_key]}] | ||
429 | ) | ||
430 | |||
397 | emit_signal("client_connected") | 431 | emit_signal("client_connected") |
398 | 432 | ||
399 | elif cmd == "ConnectionRefused": | 433 | elif cmd == "ConnectionRefused": |
@@ -453,6 +487,8 @@ func _on_data(): | |||
453 | i += 1 | 487 | i += 1 |
454 | 488 | ||
455 | elif cmd == "PrintJSON": | 489 | elif cmd == "PrintJSON": |
490 | parse_printjson(message) | ||
491 | |||
456 | if ( | 492 | if ( |
457 | !message.has("receiving") | 493 | !message.has("receiving") |
458 | or !message.has("item") | 494 | or !message.has("item") |
@@ -489,9 +525,12 @@ func _on_data(): | |||
489 | ) | 525 | ) |
490 | else: | 526 | else: |
491 | if message["receiving"] != _slot: | 527 | if message["receiving"] != _slot: |
492 | messages.showMessage( | 528 | var sentMsg = ( |
493 | "Sent [color=%s]%s[/color] to %s" % [item_color, item_name, player_name] | 529 | "Sent [color=%s]%s[/color] to %s" % [item_color, item_name, player_name] |
494 | ) | 530 | ) |
531 | if _hinted_locations.has(message["item"]["location"]): | ||
532 | sentMsg += " ([color=#fafad2]Hinted![/color])" | ||
533 | messages.showMessage(sentMsg) | ||
495 | 534 | ||
496 | elif cmd == "Bounced": | 535 | elif cmd == "Bounced": |
497 | if ( | 536 | if ( |
@@ -512,8 +551,15 @@ func _on_data(): | |||
512 | get_tree().get_root().get_node("Spatial/player/pause_menu")._reload() | 551 | get_tree().get_root().get_node("Spatial/player/pause_menu")._reload() |
513 | 552 | ||
514 | elif cmd == "SetReply": | 553 | elif cmd == "SetReply": |
515 | if message.has("key") and message["key"] == ("Lingo_%d_Paintings" % _slot): | 554 | if message.has("key"): |
516 | _checked_paintings = message["value"] | 555 | if message["key"] == ("Lingo_%d_Paintings" % _slot): |
556 | _checked_paintings = message["value"] | ||
557 | elif message["key"] == _hints_key: | ||
558 | parseHints(message["value"]) | ||
559 | |||
560 | elif cmd == "Retrieved": | ||
561 | if message.has("keys") and message["keys"].has(_hints_key): | ||
562 | parseHints(message["keys"][_hints_key]) | ||
517 | 563 | ||
518 | 564 | ||
519 | func _process(_delta): | 565 | func _process(_delta): |
@@ -567,7 +613,8 @@ func saveLocaldata(): | |||
567 | effects_node.slowness_remaining, | 613 | effects_node.slowness_remaining, |
568 | effects_node.iceland_remaining, | 614 | effects_node.iceland_remaining, |
569 | effects_node.atbash_remaining, | 615 | effects_node.atbash_remaining, |
570 | _geronimo_skip | 616 | _geronimo_skip, |
617 | effects_node.speed_boosts_remaining, | ||
571 | ] | 618 | ] |
572 | file.store_var(data, true) | 619 | file.store_var(data, true) |
573 | file.close() | 620 | file.close() |
@@ -686,6 +733,10 @@ func setValue(key, value, operation = "replace"): | |||
686 | ) | 733 | ) |
687 | 734 | ||
688 | 735 | ||
736 | func say(textdata): | ||
737 | sendMessage([{"cmd": "Say", "text": textdata}]) | ||
738 | |||
739 | |||
689 | func completedGoal(): | 740 | func completedGoal(): |
690 | sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL | 741 | sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL |
691 | 742 | ||
@@ -729,6 +780,18 @@ func processItem(item, index, from, flags): | |||
729 | for door_id in gamedata.door_ids_by_item_id[int(item)]: | 780 | for door_id in gamedata.door_ids_by_item_id[int(item)]: |
730 | doorsNode.get_node(door_id).openDoor() | 781 | doorsNode.get_node(door_id).openDoor() |
731 | 782 | ||
783 | if gamedata.panel_ids_by_item_id.has(int(item)): | ||
784 | var panel_ids = gamedata.panel_ids_by_item_id[int(item)] | ||
785 | if wasGeneratedOnVersion(0, 5, 1): | ||
786 | var extradata = get_node("Extradata") | ||
787 | if extradata.panels_mode_051_panel_fixes.has(int(item)): | ||
788 | panel_ids = extradata.panels_mode_051_panel_fixes[int(item)] | ||
789 | |||
790 | var panelsNode = get_tree().get_root().get_node("Spatial/Panels") | ||
791 | for panel_id in panel_ids: | ||
792 | panelsNode.get_node(panel_id).get_node("AP_Panel").locked = false | ||
793 | emit_signal("evaluate_solvability") | ||
794 | |||
732 | if gamedata.painting_ids_by_item_id.has(int(item)): | 795 | if gamedata.painting_ids_by_item_id.has(int(item)): |
733 | var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings") | 796 | var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings") |
734 | var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings") | 797 | var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings") |
@@ -749,15 +812,34 @@ func processItem(item, index, from, flags): | |||
749 | warpsNode.get_node(warp_id).unlock_warp() | 812 | warpsNode.get_node(warp_id).unlock_warp() |
750 | 813 | ||
751 | # Handle progressive items. | 814 | # Handle progressive items. |
752 | if int(item) in gamedata.items_by_progressive_id.keys(): | 815 | var is_progressive_door = int(item) in gamedata.door_items_by_progressive_id |
816 | var is_progressive_panel = int(item) in gamedata.panel_items_by_progressive_id | ||
817 | var progitems = null | ||
818 | var prognames = null | ||
819 | |||
820 | if is_progressive_door and is_progressive_panel: | ||
821 | if _door_shuffle: | ||
822 | progitems = gamedata.door_items_by_progressive_id[int(item)] | ||
823 | prognames = door_progressive_items | ||
824 | else: | ||
825 | progitems = gamedata.panel_items_by_progressive_id[int(item)] | ||
826 | prognames = panel_progressive_items | ||
827 | elif is_progressive_door: | ||
828 | progitems = gamedata.door_items_by_progressive_id[int(item)] | ||
829 | prognames = door_progressive_items | ||
830 | elif is_progressive_panel: | ||
831 | progitems = gamedata.panel_items_by_progressive_id[int(item)] | ||
832 | prognames = panel_progressive_items | ||
833 | |||
834 | if progitems != null: | ||
753 | if not int(item) in _progressive_progress: | 835 | if not int(item) in _progressive_progress: |
754 | _progressive_progress[int(item)] = 0 | 836 | _progressive_progress[int(item)] = 0 |
755 | 837 | ||
756 | if _progressive_progress[int(item)] < gamedata.items_by_progressive_id[int(item)].size(): | 838 | if _progressive_progress[int(item)] < progitems.size(): |
757 | var subitems = gamedata.items_by_progressive_id[int(item)] | 839 | var subitem_id = progitems[_progressive_progress[int(item)]] |
758 | var subitem_id = subitems[_progressive_progress[int(item)]] | ||
759 | global._print("Subitem: %d" % subitem_id) | 840 | global._print("Subitem: %d" % subitem_id) |
760 | processItem(subitem_id, null, null, null) | 841 | processItem(subitem_id, null, null, null) |
842 | item_name += " (%s)" % prognames[item_name][_progressive_progress[int(item)]] | ||
761 | _progressive_progress[int(item)] += 1 | 843 | _progressive_progress[int(item)] += 1 |
762 | 844 | ||
763 | if _color_shuffle and color_items.has(_item_id_to_name["Lingo"][item]): | 845 | if _color_shuffle and color_items.has(_item_id_to_name["Lingo"][item]): |
@@ -771,10 +853,6 @@ func processItem(item, index, from, flags): | |||
771 | _last_new_item = index | 853 | _last_new_item = index |
772 | saveLocaldata() | 854 | saveLocaldata() |
773 | 855 | ||
774 | if item_name in progressive_items: | ||
775 | var subitem = progressive_items[item_name][_progressive_progress[int(item)] - 1] | ||
776 | item_name += " (%s)" % subitem | ||
777 | |||
778 | var player_name = "Unknown" | 856 | var player_name = "Unknown" |
779 | if _player_name_by_slot.has(from): | 857 | if _player_name_by_slot.has(from): |
780 | player_name = _player_name_by_slot[from] | 858 | player_name = _player_name_by_slot[from] |
@@ -789,12 +867,14 @@ func processItem(item, index, from, flags): | |||
789 | ) | 867 | ) |
790 | 868 | ||
791 | var effects_node = get_tree().get_root().get_node("Spatial/AP_Effects") | 869 | var effects_node = get_tree().get_root().get_node("Spatial/AP_Effects") |
792 | if item_name == "Slowness Trap": | 870 | if item_name == "Slowness Trap" and !_speed_boost_mode: |
793 | effects_node.trigger_slowness_trap() | 871 | effects_node.trigger_slowness_trap() |
794 | if item_name == "Iceland Trap": | 872 | if item_name == "Iceland Trap": |
795 | effects_node.trigger_iceland_trap() | 873 | effects_node.trigger_iceland_trap() |
796 | if item_name == "Atbash Trap": | 874 | if item_name == "Atbash Trap": |
797 | effects_node.trigger_atbash_trap() | 875 | effects_node.trigger_atbash_trap() |
876 | if item_name == "Speed Boost" and _speed_boost_mode: | ||
877 | effects_node.trigger_speed_boost() | ||
798 | if item_name == "Puzzle Skip": | 878 | if item_name == "Puzzle Skip": |
799 | _puzzle_skips += 1 | 879 | _puzzle_skips += 1 |
800 | 880 | ||
@@ -845,6 +925,14 @@ func checkPainting(painting_id): | |||
845 | setValue("Paintings", [painting_id], "add") | 925 | setValue("Paintings", [painting_id], "add") |
846 | 926 | ||
847 | 927 | ||
928 | func parseHints(hints): | ||
929 | _hinted_locations.clear() | ||
930 | |||
931 | for hint in hints: | ||
932 | if hint["finding_player"] == int(_slot): | ||
933 | _hinted_locations.append(hint["location"]) | ||
934 | |||
935 | |||
848 | func colorForItemType(flags): | 936 | func colorForItemType(flags): |
849 | var int_flags = int(flags) | 937 | var int_flags = int(flags) |
850 | if int_flags & 1: # progression | 938 | if int_flags & 1: # progression |
@@ -857,6 +945,47 @@ func colorForItemType(flags): | |||
857 | return "#14de9e" | 945 | return "#14de9e" |
858 | 946 | ||
859 | 947 | ||
948 | func parse_printjson(message): | ||
949 | var parts = [] | ||
950 | for message_part in message["data"]: | ||
951 | if !message_part.has("type") and message_part.has("text"): | ||
952 | parts.append(message_part["text"]) | ||
953 | elif message_part["type"] == "player_id": | ||
954 | if int(message_part["text"]) == _slot: | ||
955 | parts.append("[color=#ee00ee]%s[/color]" % _player_name_by_slot[_slot]) | ||
956 | else: | ||
957 | var from = float(message_part["text"]) | ||
958 | parts.append("[color=#fafad2]%s[/color]" % _player_name_by_slot[from]) | ||
959 | elif message_part["type"] == "item_id": | ||
960 | var item_name = "Unknown" | ||
961 | var item_player_game = _game_by_player[message_part["player"]] | ||
962 | if _item_id_to_name[item_player_game].has(float(message_part["text"])): | ||
963 | item_name = _item_id_to_name[item_player_game][float(message_part["text"])] | ||
964 | |||
965 | parts.append( | ||
966 | "[color=%s]%s[/color]" % [colorForItemType(message_part["flags"]), item_name] | ||
967 | ) | ||
968 | elif message_part["type"] == "location_id": | ||
969 | var location_name = "Unknown" | ||
970 | var location_player_game = _game_by_player[message_part["player"]] | ||
971 | if _location_id_to_name[location_player_game].has(float(message_part["text"])): | ||
972 | location_name = _location_id_to_name[location_player_game][float( | ||
973 | message_part["text"] | ||
974 | )] | ||
975 | |||
976 | parts.append("[color=#00ff7f]%s[/color]" % location_name) | ||
977 | elif message_part.has("text"): | ||
978 | parts.append(message_part["text"]) | ||
979 | |||
980 | var textclient_node = get_tree().get_root().get_node_or_null("Spatial/AP_TextClient") | ||
981 | if textclient_node != null: | ||
982 | textclient_node.parse_printjson("".join(parts)) | ||
983 | |||
984 | |||
985 | func get_player_name(): | ||
986 | return _player_name_by_slot[_slot] | ||
987 | |||
988 | |||
860 | func compareVersion(lhs, rhs): | 989 | func compareVersion(lhs, rhs): |
861 | if lhs["major"] == rhs["major"]: | 990 | if lhs["major"] == rhs["major"]: |
862 | if lhs["minor"] == rhs["minor"]: | 991 | if lhs["minor"] == rhs["minor"]: |
@@ -869,3 +998,11 @@ func compareVersion(lhs, rhs): | |||
869 | 998 | ||
870 | func wasGeneratedBeforeVersion(major, minor, build): | 999 | func wasGeneratedBeforeVersion(major, minor, build): |
871 | return compareVersion(_gen_version, {"major": major, "minor": minor, "build": build}) | 1000 | return compareVersion(_gen_version, {"major": major, "minor": minor, "build": build}) |
1001 | |||
1002 | |||
1003 | func wasGeneratedOnVersion(major, minor, build): | ||
1004 | return ( | ||
1005 | _gen_version["major"] == major | ||
1006 | and _gen_version["minor"] == minor | ||
1007 | and _gen_version["build"] == build | ||
1008 | ) | ||
diff --git a/Archipelago/effects.gd b/Archipelago/effects.gd index 1e2e311..341a783 100644 --- a/Archipelago/effects.gd +++ b/Archipelago/effects.gd | |||
@@ -5,6 +5,7 @@ var effect_running = false | |||
5 | var slowness_remaining = 0 | 5 | var slowness_remaining = 0 |
6 | var iceland_remaining = 0 | 6 | var iceland_remaining = 0 |
7 | var atbash_remaining = 0 | 7 | var atbash_remaining = 0 |
8 | var speed_boosts_remaining = 0 | ||
8 | var queued_iceland = 0 | 9 | var queued_iceland = 0 |
9 | var skip_available = false | 10 | var skip_available = false |
10 | var puzzle_focused = false | 11 | var puzzle_focused = false |
@@ -48,6 +49,12 @@ func _ready(): | |||
48 | add_child(slowness_timer) | 49 | add_child(slowness_timer) |
49 | slowness_timer.connect("timeout", self, "_tick_slowness") | 50 | slowness_timer.connect("timeout", self, "_tick_slowness") |
50 | 51 | ||
52 | var speed_boost_timer = Timer.new() | ||
53 | speed_boost_timer.name = "SpeedBoostTimer" | ||
54 | speed_boost_timer.wait_time = 1.0 | ||
55 | add_child(speed_boost_timer) | ||
56 | speed_boost_timer.connect("timeout", self, "_tick_speed_boost") | ||
57 | |||
51 | var iceland_timer = Timer.new() | 58 | var iceland_timer = Timer.new() |
52 | iceland_timer.name = "IcelandTimer" | 59 | iceland_timer.name = "IcelandTimer" |
53 | iceland_timer.wait_time = 1.0 | 60 | iceland_timer.wait_time = 1.0 |
@@ -63,6 +70,12 @@ func activate(): | |||
63 | 70 | ||
64 | queued_iceland = 0 | 71 | queued_iceland = 0 |
65 | 72 | ||
73 | var apclient = global.get_node("Archipelago") | ||
74 | if apclient._speed_boost_mode: | ||
75 | var player = get_tree().get_root().get_node("Spatial/player") | ||
76 | player.walk_speed = orig_walk / 2.0 | ||
77 | player.run_speed = orig_run / 2.0 | ||
78 | |||
66 | 79 | ||
67 | func trigger_slowness_trap(length = 30): | 80 | func trigger_slowness_trap(length = 30): |
68 | if slowness_remaining == 0: | 81 | if slowness_remaining == 0: |
@@ -79,6 +92,21 @@ func trigger_slowness_trap(length = 30): | |||
79 | apclient.saveLocaldata() | 92 | apclient.saveLocaldata() |
80 | 93 | ||
81 | 94 | ||
95 | func trigger_speed_boost(length = 20): | ||
96 | if speed_boosts_remaining == 0: | ||
97 | var player = get_tree().get_root().get_node("Spatial/player") | ||
98 | player.walk_speed = orig_walk | ||
99 | player.run_speed = orig_run | ||
100 | |||
101 | $SpeedBoostTimer.start() | ||
102 | |||
103 | speed_boosts_remaining += length | ||
104 | text_dirty = true | ||
105 | |||
106 | var apclient = global.get_node("Archipelago") | ||
107 | apclient.saveLocaldata() | ||
108 | |||
109 | |||
82 | func trigger_iceland_trap(length = 60): | 110 | func trigger_iceland_trap(length = 60): |
83 | if not activated: | 111 | if not activated: |
84 | queued_iceland += length | 112 | queued_iceland += length |
@@ -99,7 +127,7 @@ func trigger_iceland_trap(length = 60): | |||
99 | 127 | ||
100 | 128 | ||
101 | func trigger_atbash_trap(): | 129 | func trigger_atbash_trap(): |
102 | var newly_atbash = (atbash_remaining == 0) | 130 | var newly_atbash = atbash_remaining == 0 |
103 | atbash_remaining += 1 | 131 | atbash_remaining += 1 |
104 | 132 | ||
105 | if newly_atbash: | 133 | if newly_atbash: |
@@ -121,7 +149,7 @@ func deactivate_atbash_trap(): | |||
121 | apclient.evaluateSolvability() | 149 | apclient.evaluateSolvability() |
122 | 150 | ||
123 | text_dirty = true | 151 | text_dirty = true |
124 | 152 | ||
125 | var apclient = global.get_node("Archipelago") | 153 | var apclient = global.get_node("Archipelago") |
126 | apclient.saveLocaldata() | 154 | apclient.saveLocaldata() |
127 | 155 | ||
@@ -192,23 +220,37 @@ func _tick_slowness(): | |||
192 | player.run_speed = orig_run | 220 | player.run_speed = orig_run |
193 | 221 | ||
194 | $SlownessTimer.stop() | 222 | $SlownessTimer.stop() |
195 | 223 | ||
196 | if slowness_remaining % 5 == 0: | 224 | if slowness_remaining % 5 == 0: |
197 | var apclient = global.get_node("Archipelago") | 225 | var apclient = global.get_node("Archipelago") |
198 | apclient.saveLocaldata() | 226 | apclient.saveLocaldata() |
199 | 227 | ||
200 | 228 | ||
229 | func _tick_speed_boost(): | ||
230 | speed_boosts_remaining -= 1 | ||
231 | text_dirty = true | ||
232 | |||
233 | if speed_boosts_remaining == 0: | ||
234 | var player = get_tree().get_root().get_node("Spatial/player") | ||
235 | player.walk_speed = orig_walk / 2.0 | ||
236 | player.run_speed = orig_run / 2.0 | ||
237 | |||
238 | $SpeedBoostTimer.stop() | ||
239 | |||
240 | if speed_boosts_remaining % 5 == 0: | ||
241 | var apclient = global.get_node("Archipelago") | ||
242 | apclient.saveLocaldata() | ||
243 | |||
244 | |||
201 | func _tick_iceland(): | 245 | func _tick_iceland(): |
202 | iceland_remaining -= 1 | 246 | iceland_remaining -= 1 |
203 | text_dirty = true | 247 | text_dirty = true |
204 | 248 | ||
205 | if iceland_remaining == 0: | 249 | if iceland_remaining == 0: |
206 | get_tree().get_root().get_node("Spatial/player/pivot/camera").set_environment( | 250 | get_tree().get_root().get_node("Spatial/player/pivot/camera").set_environment(orig_env) |
207 | orig_env | ||
208 | ) | ||
209 | 251 | ||
210 | $IcelandTimer.stop() | 252 | $IcelandTimer.stop() |
211 | 253 | ||
212 | if iceland_remaining % 5 == 0: | 254 | if iceland_remaining % 5 == 0: |
213 | var apclient = global.get_node("Archipelago") | 255 | var apclient = global.get_node("Archipelago") |
214 | apclient.saveLocaldata() | 256 | apclient.saveLocaldata() |
@@ -220,9 +262,11 @@ func _process(_delta): | |||
220 | if wallcast.is_colliding(): | 262 | if wallcast.is_colliding(): |
221 | var player = get_tree().get_root().get_node("Spatial/player") | 263 | var player = get_tree().get_root().get_node("Spatial/player") |
222 | var puzzlecast = player.get_node("pivot/camera/RayCast_sight") | 264 | var puzzlecast = player.get_node("pivot/camera/RayCast_sight") |
223 | var distance = puzzlecast.get_collision_point().distance_to(wallcast.get_collision_point()) | 265 | var distance = puzzlecast.get_collision_point().distance_to( |
266 | wallcast.get_collision_point() | ||
267 | ) | ||
224 | should_nbw = (distance < 0.05) | 268 | should_nbw = (distance < 0.05) |
225 | 269 | ||
226 | if should_nbw != not_behind_wall: | 270 | if should_nbw != not_behind_wall: |
227 | not_behind_wall = should_nbw | 271 | not_behind_wall = should_nbw |
228 | text_dirty = true | 272 | text_dirty = true |
@@ -239,6 +283,10 @@ func _process(_delta): | |||
239 | if not text.empty(): | 283 | if not text.empty(): |
240 | text += "\n" | 284 | text += "\n" |
241 | text += "Slowness: %d seconds" % slowness_remaining | 285 | text += "Slowness: %d seconds" % slowness_remaining |
286 | if speed_boosts_remaining > 0: | ||
287 | if not text.empty(): | ||
288 | text += "\n" | ||
289 | text += "Speed Boost: %d seconds" % speed_boosts_remaining | ||
242 | if iceland_remaining > 0: | 290 | if iceland_remaining > 0: |
243 | if not text.empty(): | 291 | if not text.empty(): |
244 | text += "\n" | 292 | text += "\n" |
diff --git a/Archipelago/extradata.gd b/Archipelago/extradata.gd index 89c41d2..2e26eb2 100644 --- a/Archipelago/extradata.gd +++ b/Archipelago/extradata.gd | |||
@@ -93,3 +93,87 @@ var proxies = { | |||
93 | "Open Areas/Panel_rise_horizon": ["Open Areas/Panel_son_horizon"], | 93 | "Open Areas/Panel_rise_horizon": ["Open Areas/Panel_son_horizon"], |
94 | "Open Areas/Panel_son_sunrise": ["Open Areas/Panel_rise_sunrise"] | 94 | "Open Areas/Panel_son_sunrise": ["Open Areas/Panel_rise_sunrise"] |
95 | } | 95 | } |
96 | |||
97 | var panels_mode_051_panel_fixes = { | ||
98 | 444647: | ||
99 | [ | ||
100 | "Backside Room/Panel_three_three", | ||
101 | "Backside Room/Panel_three_three_2", | ||
102 | "Backside Room/Panel_three_three_3", | ||
103 | "Backside Room/Panel_four_four_3", | ||
104 | "Backside Room/Panel_four_four_2", | ||
105 | "Backside Room/Panel_four_four_4" | ||
106 | ], | ||
107 | 444648: | ||
108 | [ | ||
109 | "Backside Room/Panel_four_four", | ||
110 | "Backside Room/Panel_five_five_5", | ||
111 | "Backside Room/Panel_five_five_4" | ||
112 | ], | ||
113 | 444649: | ||
114 | [ | ||
115 | "Backside Room/Panel_five_five", | ||
116 | "Backside Room/Panel_five_five_3", | ||
117 | "Backside Room/Panel_five_five_2", | ||
118 | "Backside Room/Panel_six_six_4" | ||
119 | ], | ||
120 | 444650: | ||
121 | [ | ||
122 | "Backside Room/Panel_six_six", | ||
123 | "Backside Room/Panel_six_six_3", | ||
124 | "Backside Room/Panel_six_six_2", | ||
125 | "Backside Room/Panel_six_six_5", | ||
126 | "Backside Room/Panel_six_six_6", | ||
127 | "Backside Room/Panel_seven_seven_5", | ||
128 | "Backside Room/Panel_seven_seven_6" | ||
129 | ], | ||
130 | 444651: | ||
131 | [ | ||
132 | "Backside Room/Panel_seven_seven", | ||
133 | "Backside Room/Panel_seven_seven_2", | ||
134 | "Backside Room/Panel_seven_seven_7", | ||
135 | "Backside Room/Panel_seven_seven_3", | ||
136 | "Backside Room/Panel_seven_seven_4", | ||
137 | "Backside Room/Panel_eight_eight_8", | ||
138 | "Backside Room/Panel_eight_eight_5", | ||
139 | "Backside Room/Panel_eight_eight_3", | ||
140 | "Backside Room/Panel_eight_eight_7" | ||
141 | ], | ||
142 | 444652: | ||
143 | [ | ||
144 | "Backside Room/Panel_eight_eight", | ||
145 | "Backside Room/Panel_eight_eight_2", | ||
146 | "Backside Room/Panel_eight_eight_4", | ||
147 | "Backside Room/Panel_eight_eight_6", | ||
148 | "Backside Room/Panel_nine_nine_3", | ||
149 | "Backside Room/Panel_nine_nine_8", | ||
150 | "Backside Room/Panel_nine_nine_4", | ||
151 | "Backside Room/Panel_nine_nine_5", | ||
152 | "Backside Room/Panel_nine_nine_2" | ||
153 | ], | ||
154 | 444653: | ||
155 | [ | ||
156 | "Backside Room/Panel_nine_nine", | ||
157 | "Backside Room/Panel_nine_nine_6", | ||
158 | "Backside Room/Panel_nine_nine_8", | ||
159 | "Backside Room/Panel_nine_nine_9", | ||
160 | "Backside Room/Panel_nine_nine_7" | ||
161 | ] | ||
162 | } | ||
163 | |||
164 | var panels_mode_051_door_fixes = { | ||
165 | "Door_four_hider": ["Door_four_hider_3", "Door_four_hider_4", "Door_four_hider_2"], | ||
166 | "Door_five_hider": ["Door_five_hider_4", "Door_five_hider_5"], | ||
167 | "Door_six_hider": ["Door_six_hider_4"], | ||
168 | "Door_seven_hider": ["Door_seven_hider_6", "Door_seven_hider_5"], | ||
169 | "Door_eight_hider": | ||
170 | ["Door_eight_hider_7", "Door_eight_hider_8", "Door_eight_hider_3", "Door_eight_hider_5"], | ||
171 | "Door_nine_hider": | ||
172 | [ | ||
173 | "Door_nine_hider_3", | ||
174 | "Door_nine_hider_8", | ||
175 | "Door_nine_hider_4", | ||
176 | "Door_nine_hider_5", | ||
177 | "Door_nine_hider_2" | ||
178 | ] | ||
179 | } | ||
diff --git a/Archipelago/load.gd b/Archipelago/load.gd index be209c3..8559b10 100644 --- a/Archipelago/load.gd +++ b/Archipelago/load.gd | |||
@@ -554,6 +554,20 @@ func _load(): | |||
554 | proxynode.exact_proxy = true | 554 | proxynode.exact_proxy = true |
555 | proxynode.request_ready() | 555 | proxynode.request_ready() |
556 | oldparent.add_child(proxynode) | 556 | oldparent.add_child(proxynode) |
557 | |||
558 | # If the world was generated on 0.5.1, apply the hotfix for the number hunt doors. | ||
559 | if apclient._panel_door_shuffle && apclient.wasGeneratedOnVersion(0, 5, 1): | ||
560 | var number_hunt_parent = get_node("Doors/Count Up Room Area Doors") | ||
561 | var extradata_051_fix = apclient.get_node("Extradata").panels_mode_051_door_fixes | ||
562 | for template_door_path in extradata_051_fix: | ||
563 | var template_door = number_hunt_parent.get_node(template_door_path) | ||
564 | var impacted_doors = extradata_051_fix[template_door_path] | ||
565 | for impacted_door_path in impacted_doors: | ||
566 | var impacted_door = number_hunt_parent.get_node(impacted_door_path) | ||
567 | var copied_door = impacted_door.duplicate() | ||
568 | copied_door.panels = template_door.panels | ||
569 | number_hunt_parent.add_child(copied_door) | ||
570 | impacted_door.queue_free() | ||
557 | 571 | ||
558 | # Attach a script to every panel so that we can do things like conditionally | 572 | # Attach a script to every panel so that we can do things like conditionally |
559 | # disable them. | 573 | # disable them. |
@@ -567,6 +581,8 @@ func _load(): | |||
567 | var script_instance = panel_script.new() | 581 | var script_instance = panel_script.new() |
568 | script_instance.name = "AP_Panel" | 582 | script_instance.name = "AP_Panel" |
569 | script_instance.data = panel | 583 | script_instance.data = panel |
584 | if apclient._panel_door_shuffle and gamedata.mentioned_panels.has(panel["id"]): | ||
585 | script_instance.locked = true | ||
570 | panel_node.add_child(script_instance) | 586 | panel_node.add_child(script_instance) |
571 | apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability") | 587 | apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability") |
572 | 588 | ||
@@ -666,9 +682,16 @@ func _load(): | |||
666 | effects.set_name("AP_Effects") | 682 | effects.set_name("AP_Effects") |
667 | self.add_child(effects) | 683 | self.add_child(effects) |
668 | 684 | ||
685 | # Create the textclient node. | ||
686 | var textclient_script = apclient.SCRIPT_textclient | ||
687 | var textclient = textclient_script.new() | ||
688 | textclient.set_name("AP_TextClient") | ||
689 | self.add_child(textclient) | ||
690 | |||
669 | # Create the multiplayer node, if needed. | 691 | # Create the multiplayer node, if needed. |
670 | if apclient.enable_multiplayer: | 692 | if apclient.enable_multiplayer: |
671 | var multiplayer_node = apclient.SCRIPT_multiplayer.new() | 693 | var multiplayer_node = apclient.SCRIPT_multiplayer.new() |
694 | multiplayer_node.name = "Multiplayer" | ||
672 | multiplayer_node.ghost_mode = true | 695 | multiplayer_node.ghost_mode = true |
673 | add_child(multiplayer_node) | 696 | add_child(multiplayer_node) |
674 | 697 | ||
diff --git a/Archipelago/multiplayer.gd b/Archipelago/multiplayer.gd index 0bc2241..c2d9875 100644 --- a/Archipelago/multiplayer.gd +++ b/Archipelago/multiplayer.gd | |||
@@ -1,5 +1,8 @@ | |||
1 | extends "res://scripts/multiplayer.gd" | 1 | extends "res://scripts/multiplayer.gd" |
2 | 2 | ||
3 | var queued_messages = [] | ||
4 | var queued_messages_mutex = Mutex.new() | ||
5 | |||
3 | 6 | ||
4 | func _request_lobby_list(): | 7 | func _request_lobby_list(): |
5 | var apclient = global.get_node("Archipelago") | 8 | var apclient = global.get_node("Archipelago") |
@@ -41,3 +44,55 @@ func _update_lobby_members(): | |||
41 | if member_id != player_steam_id and member_id in active_lobby_members: | 44 | if member_id != player_steam_id and member_id in active_lobby_members: |
42 | var slot_name = Steam.getLobbyMemberData(active_lobby_id, member_id, "slot_name") | 45 | var slot_name = Steam.getLobbyMemberData(active_lobby_id, member_id, "slot_name") |
43 | active_lobby_members[member_id].steam_name = slot_name | 46 | active_lobby_members[member_id].steam_name = slot_name |
47 | |||
48 | |||
49 | func say(text): | ||
50 | queued_messages_mutex.lock() | ||
51 | queued_messages.append(text) | ||
52 | queued_messages_mutex.unlock() | ||
53 | |||
54 | |||
55 | func _physics_process(_delta): | ||
56 | if queued_messages_mutex.try_lock() == OK: | ||
57 | if queued_messages.empty(): | ||
58 | queued_messages_mutex.unlock() | ||
59 | else: | ||
60 | var messages = queued_messages.duplicate() | ||
61 | queued_messages = [] | ||
62 | queued_messages_mutex.unlock() | ||
63 | |||
64 | var player = get_tree().get_root().get_node("Spatial/player") | ||
65 | var space_state = get_tree().get_root().get_world().direct_space_state | ||
66 | var nearby_members = [] | ||
67 | for member_id in active_lobby_members.keys(): | ||
68 | var other_member = active_lobby_members[member_id] | ||
69 | var ray = space_state.intersect_ray( | ||
70 | player.global_translation, other_member.global_translation, [player], 0b0101 | ||
71 | ) | ||
72 | if !("collider" in ray) or ray["collider"] == other_member: | ||
73 | # Visible! | ||
74 | nearby_members.append(member_id) | ||
75 | |||
76 | var apclient = global.get_node("Archipelago") | ||
77 | var player_name = apclient.get_player_name() | ||
78 | for member_id in nearby_members: | ||
79 | for msg in messages: | ||
80 | _send_p2p_packet( | ||
81 | {"solves": [{"say": msg, "from": player_name}]}, | ||
82 | member_id, | ||
83 | Steam.P2P_SEND_RELIABLE, | ||
84 | true | ||
85 | ) | ||
86 | |||
87 | |||
88 | # I'm completely hijacking this callback, since we're in ghost mode and it won't be called normally. | ||
89 | func _receive_solve(data): | ||
90 | if "say" in data: | ||
91 | get_tree().get_root().get_node("Spatial/AP_TextClient").parse_printjson( | ||
92 | "[LOCAL] [color=#fafad2]%s[/color]: %s" % [data["from"], data["say"]] | ||
93 | ) | ||
94 | messages.showMessage("[color=#fafad2]%s[/color]: %s" % [data["from"], data["say"]]) | ||
95 | |||
96 | |||
97 | func _send_hi(_discard): | ||
98 | pass | ||
diff --git a/Archipelago/panel.gd b/Archipelago/panel.gd index fc5963a..ce632c5 100644 --- a/Archipelago/panel.gd +++ b/Archipelago/panel.gd | |||
@@ -5,6 +5,7 @@ var orig_text = "" | |||
5 | var atbash_text = "" | 5 | var atbash_text = "" |
6 | var orig_color = Color(0, 0, 0, 0) | 6 | var orig_color = Color(0, 0, 0, 0) |
7 | var solvable = true | 7 | var solvable = true |
8 | var locked = false | ||
8 | 9 | ||
9 | const kAtbashPre = "abcdefghijklmnopqrstuvwxyz1234567890+-" | 10 | const kAtbashPre = "abcdefghijklmnopqrstuvwxyz1234567890+-" |
10 | const kAtbashPost = "zyxwvutsrqponmlkjihgfedcba0987654321-+" | 11 | const kAtbashPost = "zyxwvutsrqponmlkjihgfedcba0987654321-+" |
@@ -39,7 +40,9 @@ func evaluate_solvability(): | |||
39 | solvable = true | 40 | solvable = true |
40 | var missing = [] | 41 | var missing = [] |
41 | 42 | ||
42 | if apclient._color_shuffle: | 43 | if locked: |
44 | solvable = false | ||
45 | elif apclient._color_shuffle: | ||
43 | for color in data["color"]: | 46 | for color in data["color"]: |
44 | if not apclient._has_colors.has(color): | 47 | if not apclient._has_colors.has(color): |
45 | missing.append(color) | 48 | missing.append(color) |
@@ -52,6 +55,12 @@ func evaluate_solvability(): | |||
52 | self.get_parent().get_node("Viewport/GUI/Panel/Label").text = orig_text | 55 | self.get_parent().get_node("Viewport/GUI/Panel/Label").text = orig_text |
53 | self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = true | 56 | self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = true |
54 | self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = orig_color | 57 | self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = orig_color |
58 | elif locked: | ||
59 | self.get_parent().get_node("Viewport/GUI/Panel/Label").text = "Locked" | ||
60 | self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = false | ||
61 | self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = Color( | ||
62 | 0.2, 0.7, 0.7 | ||
63 | ) | ||
55 | else: | 64 | else: |
56 | var missing_text = "Missing: " | 65 | var missing_text = "Missing: " |
57 | for thing in missing: | 66 | for thing in missing: |
diff --git a/Archipelago/pause_menu.gd b/Archipelago/pause_menu.gd index 40994d9..62ba6c3 100644 --- a/Archipelago/pause_menu.gd +++ b/Archipelago/pause_menu.gd | |||
@@ -6,3 +6,8 @@ func _main_menu(): | |||
6 | apclient.disconnect_from_ap() | 6 | apclient.disconnect_from_ap() |
7 | 7 | ||
8 | ._main_menu() | 8 | ._main_menu() |
9 | |||
10 | |||
11 | func _pause_game(): | ||
12 | get_tree().get_root().get_node("Spatial/AP_TextClient").dismiss() | ||
13 | ._pause_game() | ||
diff --git a/Archipelago/settings_screen.gd b/Archipelago/settings_screen.gd index 956d966..ec3b5f1 100644 --- a/Archipelago/settings_screen.gd +++ b/Archipelago/settings_screen.gd | |||
@@ -38,6 +38,7 @@ func _ready(): | |||
38 | apclient_instance.SCRIPT_panel = load("user://maps/Archipelago/panel.gd") | 38 | apclient_instance.SCRIPT_panel = load("user://maps/Archipelago/panel.gd") |
39 | var pilg_term = load("user://maps/Archipelago/pilgrimage_terminator.gd") | 39 | var pilg_term = load("user://maps/Archipelago/pilgrimage_terminator.gd") |
40 | apclient_instance.SCRIPT_pilgrimage_terminator = pilg_term | 40 | apclient_instance.SCRIPT_pilgrimage_terminator = pilg_term |
41 | apclient_instance.SCRIPT_textclient = load("user://maps/Archipelago/textclient.gd") | ||
41 | apclient_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd") | 42 | apclient_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd") |
42 | 43 | ||
43 | var apdata = ResourceLoader.load("user://maps/Archipelago/gamedata.gd") | 44 | var apdata = ResourceLoader.load("user://maps/Archipelago/gamedata.gd") |
diff --git a/Archipelago/textclient.gd b/Archipelago/textclient.gd new file mode 100644 index 0000000..3abd9e0 --- /dev/null +++ b/Archipelago/textclient.gd | |||
@@ -0,0 +1,100 @@ | |||
1 | extends Node | ||
2 | |||
3 | var panel | ||
4 | var label | ||
5 | var entry | ||
6 | var is_open = false | ||
7 | |||
8 | |||
9 | func _ready(): | ||
10 | pause_mode = PAUSE_MODE_PROCESS | ||
11 | |||
12 | panel = Panel.new() | ||
13 | panel.set_name("Panel") | ||
14 | panel.margin_left = 100 | ||
15 | panel.margin_right = 1820 | ||
16 | panel.margin_top = 100 | ||
17 | panel.margin_bottom = 980 | ||
18 | panel.visible = false | ||
19 | add_child(panel) | ||
20 | |||
21 | label = RichTextLabel.new() | ||
22 | label.set_name("Label") | ||
23 | label.margin_left = 80 | ||
24 | label.margin_right = 1640 | ||
25 | label.margin_top = 80 | ||
26 | label.margin_bottom = 720 | ||
27 | label.scroll_following = true | ||
28 | label.selection_enabled = true | ||
29 | panel.add_child(label) | ||
30 | |||
31 | var dynamic_font = DynamicFont.new() | ||
32 | dynamic_font.font_data = load("res://fonts/Lingo.ttf") | ||
33 | dynamic_font.size = 36 | ||
34 | label.push_font(dynamic_font) | ||
35 | |||
36 | var entry_style = StyleBoxFlat.new() | ||
37 | entry_style.bg_color = Color(0.9, 0.9, 0.9, 1) | ||
38 | |||
39 | entry = LineEdit.new() | ||
40 | entry.set_name("Entry") | ||
41 | entry.margin_left = 80 | ||
42 | entry.margin_right = 1640 | ||
43 | entry.margin_top = 760 | ||
44 | entry.margin_bottom = 840 | ||
45 | entry.add_font_override("font", dynamic_font) | ||
46 | entry.add_color_override("font_color", Color(0, 0, 0, 1)) | ||
47 | entry.add_color_override("cursor_color", Color(0, 0, 0, 1)) | ||
48 | entry.add_stylebox_override("focus", entry_style) | ||
49 | panel.add_child(entry) | ||
50 | entry.connect("text_entered", self, "text_entered") | ||
51 | |||
52 | |||
53 | func _input(event): | ||
54 | if event is InputEventKey and event.pressed: | ||
55 | if event.scancode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT): | ||
56 | if !get_tree().paused: | ||
57 | is_open = true | ||
58 | get_tree().paused = true | ||
59 | Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) | ||
60 | panel.visible = true | ||
61 | entry.grab_focus() | ||
62 | get_tree().set_input_as_handled() | ||
63 | else: | ||
64 | dismiss() | ||
65 | elif event.scancode == KEY_ESCAPE: | ||
66 | if is_open: | ||
67 | dismiss() | ||
68 | get_tree().set_input_as_handled() | ||
69 | |||
70 | |||
71 | func dismiss(): | ||
72 | if is_open: | ||
73 | get_tree().paused = false | ||
74 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) | ||
75 | panel.visible = false | ||
76 | is_open = false | ||
77 | |||
78 | |||
79 | func parse_printjson(text): | ||
80 | if !label.text.empty(): | ||
81 | label.append_bbcode("\n") | ||
82 | |||
83 | label.append_bbcode(text) | ||
84 | |||
85 | |||
86 | func text_entered(text): | ||
87 | var apclient = global.get_node("Archipelago") | ||
88 | var cmd = text.trim_suffix("\n") | ||
89 | if cmd.begins_with("/say "): | ||
90 | if apclient.enable_multiplayer: | ||
91 | var msg = cmd.trim_prefix("/say ") | ||
92 | parse_printjson( | ||
93 | "[LOCAL] [color=#ee00ee]%s[/color]: %s" % [apclient.get_player_name(), msg] | ||
94 | ) | ||
95 | get_tree().get_root().get_node("Spatial/Multiplayer").say(msg) | ||
96 | else: | ||
97 | parse_printjson("Multiplayer must be enabled to use /say") | ||
98 | else: | ||
99 | apclient.say(cmd) | ||
100 | entry.text = "" | ||
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c5113a..d34321d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -1,5 +1,91 @@ | |||
1 | # lingo-archipelago Releases | 1 | # lingo-archipelago Releases |
2 | 2 | ||
3 | ## v5.0.1 - 2024-12-08 | ||
4 | |||
5 | - This update adds a workaround for | ||
6 | [a logic error in Archipelago 0.5.1](https://github.com/ArchipelagoMW/Archipelago/pull/4342). | ||
7 | If your slot is affected by this bug (if you are playing panels mode door | ||
8 | shuffle in a multiworld generated on Archipelago 0.5.1), the following changes | ||
9 | will occur: | ||
10 | - The broken panels (listed at the above link) will now unlock at one | ||
11 | Progressive Number Hunt earlier than would be expected. | ||
12 | - The doors covering the broken panels will now open when all of the numbers | ||
13 | in the previous set have been solved, instead of when the number in the | ||
14 | number hunt hallway is solved. | ||
15 | - This can lead to some non-intuitive gameplay, because the panels no longer | ||
16 | unlock the way they do in the base game. To help with this, v0.11.5 of the | ||
17 | tracker now uses the 0.5.1 broken logic, so that you can always tell what is | ||
18 | expected of you. | ||
19 | - There is already a fix for the logic error, and it will likely be included as | ||
20 | part of the next major Archipelago release. I apologize for the inconvenience. | ||
21 | |||
22 | Download: | ||
23 | [lingo-archipelago-v5.0.1.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.0.1.zip)<br/> | ||
24 | Source: [v5.0.1](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.0.1) | ||
25 | |||
26 | ## v5.0.0 - 2024-11-26 | ||
27 | |||
28 | - [Archipelago 0.5.1](https://github.com/ArchipelagoMW/Archipelago/releases/tag/0.5.1) | ||
29 | has been released! this release includes a couple of major new features for | ||
30 | Lingo: | ||
31 | - There is a new type of door shuffle called "panels mode". In panels mode, | ||
32 | the doors open when their vanilla panels are solved, but the panels | ||
33 | themselves are locked behind items. This provides a vanilla-like progression | ||
34 | route through the game, while maintaining a door shuffle amount of | ||
35 | progression gating. | ||
36 | - By default, checks that are behind your victory condition are now removed | ||
37 | from your world. | ||
38 | - There are also a number of minor tweaks and bug fixes, which you can see in | ||
39 | the above changelog. | ||
40 | - This update should retain compatibility with Archipelago 0.5.0 worlds. | ||
41 | - The messages in the bottom left corner now lets you know if an item you sent | ||
42 | out was hinted. | ||
43 | - The text in the text client is now selectable. | ||
44 | |||
45 | Download: | ||
46 | [lingo-archipelago-v5.0.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.0.0.zip)<br/> | ||
47 | Source: [v5.0.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.0.0) | ||
48 | |||
49 | ## v4.2.1 - 2024-09-21 | ||
50 | |||
51 | - The text client will no longer open if SHIFT is being held, to prevent | ||
52 | conflicts with the Steam Overlay. | ||
53 | |||
54 | Download: | ||
55 | [lingo-archipelago-v4.2.1.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v4.2.1.zip)<br/> | ||
56 | Source: [v4.2.1](https://code.fourisland.com/lingo-archipelago/tag/?h=v4.2.1) | ||
57 | |||
58 | ## v4.2.0 - 2024-09-20 | ||
59 | |||
60 | - Added a proximity chat feature. You can use the command "/say" followed by a | ||
61 | message in the text client, and it will broadcast the message only to other | ||
62 | nearby Lingo players. | ||
63 | |||
64 | Download: | ||
65 | [lingo-archipelago-v4.2.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v4.2.0.zip)<br/> | ||
66 | Source: [v4.2.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v4.2.0) | ||
67 | |||
68 | ## v4.1.0 - 2024-09-02 | ||
69 | |||
70 | - Added an in-game text client, which can be used to talk to other players and | ||
71 | issue commands, similar to the official Archipelago Text Client app. Press TAB | ||
72 | to open or close the text client. | ||
73 | |||
74 | Download: | ||
75 | [lingo-archipelago-v4.1.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v4.1.0.zip)<br/> | ||
76 | Source: [v4.1.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v4.1.0) | ||
77 | |||
78 | ## v4.0.2 - 2024-07-14 | ||
79 | |||
80 | - Multiplayer mode now shows a player's alias in addition to their slot name. | ||
81 | - Messages are now cleared out between connections. | ||
82 | - Fixed issue where traps and Geronimo state could persist when switching from | ||
83 | an active slot to a new slot. | ||
84 | |||
85 | Download: | ||
86 | [lingo-archipelago-v4.0.2.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v4.0.2.zip)<br/> | ||
87 | Source: [v4.0.2](https://code.fourisland.com/lingo-archipelago/tag/?h=v4.0.2) | ||
88 | |||
3 | ## v4.0.1 - 2024-07-02 | 89 | ## v4.0.1 - 2024-07-02 |
4 | 90 | ||
5 | - Fixed issue where paired panels would become proxied on panelsanity under | 91 | - Fixed issue where paired panels would become proxied on panelsanity under |
diff --git a/README.md b/README.md index a242f95..dcdc9cd 100644 --- a/README.md +++ b/README.md | |||
@@ -92,6 +92,14 @@ pick and choose which ones you would like to use. | |||
92 | the numbering can change. You can also specify whether access to the sunwarps | 92 | the numbering can change. You can also specify whether access to the sunwarps |
93 | should be locked behind receiving certain items. | 93 | should be locked behind receiving certain items. |
94 | 94 | ||
95 | There is also a built-in text client you can use to talk to other players and | ||
96 | issue commands. You can open and close it by pressing tab. | ||
97 | |||
98 | The command "/say" can be used in the built-in text client to send messages that | ||
99 | are only visible to players that you can currently see. Note that this is not | ||
100 | symmetric: it is possible that you may see someone who cannot see you back, if, | ||
101 | for instance, there is a door in between you and it is only open on your slot. | ||
102 | |||
95 | ## Frequently Asked Questions | 103 | ## Frequently Asked Questions |
96 | 104 | ||
97 | ### What are location checks in this game? | 105 | ### What are location checks in this game? |
diff --git a/util/generate_gamedata.rb b/util/generate_gamedata.rb index ce8df43..83099ad 100644 --- a/util/generate_gamedata.rb +++ b/util/generate_gamedata.rb | |||
@@ -12,20 +12,24 @@ CLASSIFICATION_SMALL_SPHERE_ONE = 8 | |||
12 | 12 | ||
13 | panel_to_id = {} | 13 | panel_to_id = {} |
14 | door_groups = {} | 14 | door_groups = {} |
15 | panel_groups = {} | ||
15 | warp_groups = {} | 16 | warp_groups = {} |
16 | 17 | ||
17 | panel_output = [] | 18 | panel_output = [] |
18 | door_ids_by_item_id = {} | 19 | door_ids_by_item_id = {} |
19 | painting_ids_by_item_id = {} | 20 | painting_ids_by_item_id = {} |
21 | panel_ids_by_item_id = {} | ||
20 | warp_ids_by_item_id = {} | 22 | warp_ids_by_item_id = {} |
21 | panel_ids_by_location_id = {} | 23 | panel_ids_by_location_id = {} |
22 | classification_by_location_id = {} | 24 | classification_by_location_id = {} |
23 | sunwarps = Array.new(12) {Hash.new} | 25 | sunwarps = Array.new(12) {Hash.new} |
24 | mentioned_doors = Set[] | 26 | mentioned_doors = Set[] |
25 | mentioned_paintings = Set[] | 27 | mentioned_paintings = Set[] |
28 | mentioned_panels = Set[] | ||
26 | mentioned_warps = Set[] | 29 | mentioned_warps = Set[] |
27 | painting_output = {} | 30 | painting_output = {} |
28 | items_by_progressive_id = {} | 31 | door_items_by_progressive_id = {} |
32 | panel_items_by_progressive_id = {} | ||
29 | 33 | ||
30 | ids_config = YAML.load_file(idspath) | 34 | ids_config = YAML.load_file(idspath) |
31 | 35 | ||
@@ -110,15 +114,29 @@ config.each do |room_name, room_data| | |||
110 | end | 114 | end |
111 | 115 | ||
112 | if room_data.include? "progression" | 116 | if room_data.include? "progression" |
113 | room_data["progression"].each do |progressive_item_name, progression| | 117 | room_data["progression"].each do |progressive_item_name, pdata| |
114 | progressive_id = ids_config["progression"][progressive_item_name] | 118 | progressive_id = ids_config["progression"][progressive_item_name] |
115 | items_by_progressive_id[progressive_id] = [] | ||
116 | 119 | ||
117 | progression.each do |item| | 120 | if pdata.include? "doors" |
118 | item_room_name = (item.kind_of? Hash) ? item["room"] : room_name | 121 | door_items_by_progressive_id[progressive_id] = [] |
119 | item_item_name = (item.kind_of? Hash) ? item["door"] : item | ||
120 | 122 | ||
121 | items_by_progressive_id[progressive_id] << ids_config["doors"][item_room_name][item_item_name]["item"] | 123 | pdata["doors"].each do |item| |
124 | item_room_name = (item.kind_of? Hash) ? item["room"] : room_name | ||
125 | item_item_name = (item.kind_of? Hash) ? item["door"] : item | ||
126 | |||
127 | door_items_by_progressive_id[progressive_id] << ids_config["doors"][item_room_name][item_item_name]["item"] | ||
128 | end | ||
129 | end | ||
130 | |||
131 | if pdata.include? "panel_doors" | ||
132 | panel_items_by_progressive_id[progressive_id] = [] | ||
133 | |||
134 | pdata["panel_doors"].each do |item| | ||
135 | item_room_name = (item.kind_of? Hash) ? item["room"] : room_name | ||
136 | item_item_name = (item.kind_of? Hash) ? item["panel_door"] : item | ||
137 | |||
138 | panel_items_by_progressive_id[progressive_id] << ids_config["panel_doors"][item_room_name][item_item_name] | ||
139 | end | ||
122 | end | 140 | end |
123 | end | 141 | end |
124 | end | 142 | end |
@@ -206,6 +224,26 @@ config.each do |room_name, room_data| | |||
206 | end | 224 | end |
207 | end | 225 | end |
208 | end | 226 | end |
227 | |||
228 | if room_data.include? "panel_doors" | ||
229 | room_data["panel_doors"].each do |panel_door_name, panel_door| | ||
230 | item_id = ids_config["panel_doors"][room_name][panel_door_name] | ||
231 | |||
232 | panel_ids_by_item_id[item_id] = panel_door["panels"].map do |panel_identifier| | ||
233 | other_room_name = (panel_identifier.kind_of? String) ? room_name : panel_identifier["room"] | ||
234 | other_panel_name = (panel_identifier.kind_of? String) ? panel_identifier : panel_identifier["panel"] | ||
235 | |||
236 | config[other_room_name]["panels"][other_panel_name]["id"] | ||
237 | end | ||
238 | |||
239 | mentioned_panels.merge(panel_ids_by_item_id[item_id]) | ||
240 | |||
241 | if panel_door.include? "panel_group" | ||
242 | panel_groups[panel_door["panel_group"]] ||= Set[] | ||
243 | panel_groups[panel_door["panel_group"]].merge(panel_ids_by_item_id[item_id]) | ||
244 | end | ||
245 | end | ||
246 | end | ||
209 | end | 247 | end |
210 | 248 | ||
211 | door_groups.each do |group_name, door_ids| | 249 | door_groups.each do |group_name, door_ids| |
@@ -213,6 +251,11 @@ door_groups.each do |group_name, door_ids| | |||
213 | door_ids_by_item_id[item_id] = door_ids.to_a | 251 | door_ids_by_item_id[item_id] = door_ids.to_a |
214 | end | 252 | end |
215 | 253 | ||
254 | panel_groups.each do |group_name, panel_ids| | ||
255 | item_id = ids_config["panel_groups"][group_name] | ||
256 | panel_ids_by_item_id[item_id] = panel_ids.to_a | ||
257 | end | ||
258 | |||
216 | warp_groups.each do |group_name, warp_ids| | 259 | warp_groups.each do |group_name, warp_ids| |
217 | item_id = ids_config["door_groups"][group_name] | 260 | item_id = ids_config["door_groups"][group_name] |
218 | warp_ids_by_item_id[item_id] = warp_ids.to_a | 261 | warp_ids_by_item_id[item_id] = warp_ids.to_a |
@@ -231,6 +274,12 @@ File.open(outputpath, "w") do |f| | |||
231 | "\"#{door_id}\"" | 274 | "\"#{door_id}\"" |
232 | end.join(",") + "]" | 275 | end.join(",") + "]" |
233 | end.join(",")) | 276 | end.join(",")) |
277 | f.write "}\nvar panel_ids_by_item_id = {" | ||
278 | f.write(panel_ids_by_item_id.map do |item_id, panel_ids| | ||
279 | "#{item_id}:[" + panel_ids.map do |panel_id| | ||
280 | "\"#{panel_id}\"" | ||
281 | end.join(",") + "]" | ||
282 | end.join(",")) | ||
234 | f.write "}\nvar painting_ids_by_item_id = {" | 283 | f.write "}\nvar painting_ids_by_item_id = {" |
235 | f.write(painting_ids_by_item_id.map do |item_id, painting_ids| | 284 | f.write(painting_ids_by_item_id.map do |item_id, painting_ids| |
236 | "#{item_id}:[" + painting_ids.map do |painting_id| | 285 | "#{item_id}:[" + painting_ids.map do |painting_id| |
@@ -257,6 +306,10 @@ File.open(outputpath, "w") do |f| | |||
257 | f.write(mentioned_paintings.map do |painting_id| | 306 | f.write(mentioned_paintings.map do |painting_id| |
258 | "\"#{painting_id}\"" | 307 | "\"#{painting_id}\"" |
259 | end.join(",")) | 308 | end.join(",")) |
309 | f.write "]\nvar mentioned_panels = [" | ||
310 | f.write(mentioned_panels.map do |panel_id| | ||
311 | "\"#{panel_id}\"" | ||
312 | end.join(",")) | ||
260 | f.write "]\nvar mentioned_warps = [" | 313 | f.write "]\nvar mentioned_warps = [" |
261 | f.write(mentioned_warps.map do |warp_id| | 314 | f.write(mentioned_warps.map do |warp_id| |
262 | "\"#{warp_id}\"" | 315 | "\"#{warp_id}\"" |
@@ -273,8 +326,12 @@ File.open(outputpath, "w") do |f| | |||
273 | f.write(sunwarps.map do |sunwarp| | 326 | f.write(sunwarps.map do |sunwarp| |
274 | "{\"orientation\":\"#{sunwarp["orientation"]}\",\"entrance_indicator_pos\":#{sunwarp["entrance_indicator_pos"].to_s}}" | 327 | "{\"orientation\":\"#{sunwarp["orientation"]}\",\"entrance_indicator_pos\":#{sunwarp["entrance_indicator_pos"].to_s}}" |
275 | end.join(",")) | 328 | end.join(",")) |
276 | f.write "]\nvar items_by_progressive_id = {" | 329 | f.write "]\nvar door_items_by_progressive_id = {" |
277 | f.write(items_by_progressive_id.map do |item_id, progression_ids| | 330 | f.write(door_items_by_progressive_id.map do |item_id, progression_ids| |
331 | "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" | ||
332 | end.join(",")) | ||
333 | f.write "}\nvar panel_items_by_progressive_id = {" | ||
334 | f.write(panel_items_by_progressive_id.map do |item_id, progression_ids| | ||
278 | "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" | 335 | "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" |
279 | end.join(",")) | 336 | end.join(",")) |
280 | f.write "}" | 337 | f.write "}" |