about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Archipelago/client.gd260
-rw-r--r--Archipelago/extradata.gd99
-rw-r--r--Archipelago/load.gd43
-rw-r--r--Archipelago/multiplayer.gd2
-rw-r--r--Archipelago/notifier.gd14
-rw-r--r--Archipelago/painting.gd32
-rw-r--r--Archipelago/panel.gd17
-rw-r--r--Archipelago/player.gd29
-rw-r--r--Archipelago/settings_screen.gd2
-rw-r--r--Archipelago/textclient.gd3
-rw-r--r--Archipelago/tracker.gd89
-rw-r--r--CHANGELOG.md137
-rw-r--r--README.md10
-rw-r--r--util/generate_gamedata.rb96
14 files changed, 741 insertions, 92 deletions
diff --git a/Archipelago/client.gd b/Archipelago/client.gd index 350723e..fffd8c3 100644 --- a/Archipelago/client.gd +++ b/Archipelago/client.gd
@@ -5,10 +5,10 @@ var SCRIPT_effects
5var SCRIPT_location 5var SCRIPT_location
6var SCRIPT_multiplayer 6var SCRIPT_multiplayer
7var SCRIPT_mypainting 7var SCRIPT_mypainting
8var SCRIPT_notifier
9var SCRIPT_panel 8var SCRIPT_panel
10var SCRIPT_pilgrimage_terminator 9var SCRIPT_pilgrimage_terminator
11var SCRIPT_textclient 10var SCRIPT_textclient
11var SCRIPT_tracker
12var SCRIPT_uuid 12var SCRIPT_uuid
13 13
14var ap_server = "" 14var ap_server = ""
@@ -19,12 +19,12 @@ var enable_multiplayer = false
19var track_player = false 19var track_player = false
20var connection_history = [] 20var connection_history = []
21 21
22const my_version = "4.2.0" 22const my_version = "5.4.0"
23const ap_version = {"major": 0, "minor": 5, "build": 0, "class": "Version"} 23const ap_version = {"major": 0, "minor": 5, "build": 1, "class": "Version"}
24const color_items = [ 24const color_items = [
25 "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow" 25 "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow"
26] 26]
27const progressive_items = { 27const door_progressive_items = {
28 "Progressive Orange Tower": 28 "Progressive Orange Tower":
29 ["Second Floor", "Third Floor", "Fourth Floor", "Fifth Floor", "Sixth Floor", "Seventh Floor"], 29 ["Second Floor", "Third Floor", "Fourth Floor", "Fifth Floor", "Sixth Floor", "Seventh Floor"],
30 "Progressive Art Gallery": 30 "Progressive Art Gallery":
@@ -36,6 +36,15 @@ const progressive_items = {
36 "Progressive Pilgrimage": 36 "Progressive Pilgrimage":
37 ["1 Sunwarp", "2 Sunwarp", "3 Sunwarp", "4 Sunwarp", "5 Sunwarp", "6 Sunwarp"] 37 ["1 Sunwarp", "2 Sunwarp", "3 Sunwarp", "4 Sunwarp", "5 Sunwarp", "6 Sunwarp"]
38} 38}
39const panel_progressive_items = {
40 "Progressive Hallway Room": ["First Door", "Second Door", "Third Door", "Fourth Door"],
41 "Progressive Colorful":
42 ["White", "Black", "Red", "Yellow", "Blue", "Purple", "Orange", "Green", "Brown", "Gray"],
43 "Progressive Number Hunt":
44 ["Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Zero"],
45 "Progressive Symmetry Room": ["Near Far", "Warts Straw", "Leaf Feel"],
46 "Progressive Suits Area": ["Words Sword", "Lost", "Amen Name"]
47}
39 48
40const kTHE_END = 0 49const kTHE_END = 0
41const kTHE_MASTER = 1 50const kTHE_MASTER = 1
@@ -90,6 +99,7 @@ var _localdata_file = ""
90var _death_link = false 99var _death_link = false
91var _victory_condition = 0 # THE END, THE MASTER, LEVEL 2 100var _victory_condition = 0 # THE END, THE MASTER, LEVEL 2
92var _door_shuffle = false 101var _door_shuffle = false
102var _panel_door_shuffle = false
93var _color_shuffle = false 103var _color_shuffle = false
94var _panel_shuffle = 0 # none, rearrange 104var _panel_shuffle = 0 # none, rearrange
95var _painting_shuffle = false 105var _painting_shuffle = false
@@ -121,6 +131,17 @@ var _cached_atbash = 0
121var _cached_speed_boosts = 0 131var _cached_speed_boosts = 0
122var _geronimo_skip = false 132var _geronimo_skip = false
123var _checked_paintings = [] 133var _checked_paintings = []
134var _hints_key = ""
135var _hinted_locations = []
136var _batch_messages = false
137var _held_messages = {}
138var _held_panels = []
139var _held_synced_panels = []
140var _solved_panels = []
141
142var _panelsBySolveIndex = {}
143const kPANEL_BITFIELDS = 17 # 800 / 48
144const kPANEL_BITFIELD_LENGTH = 48
124 145
125signal could_not_connect 146signal could_not_connect
126signal connect_status 147signal connect_status
@@ -176,6 +197,8 @@ func _reset_state():
176 _authenticated = false 197 _authenticated = false
177 _map_loaded = false 198 _map_loaded = false
178 _try_wss = false 199 _try_wss = false
200 _panelsBySolveIndex = {}
201 _solved_panels = []
179 202
180 203
181func _errored(): 204func _errored():
@@ -278,9 +301,15 @@ func _on_data():
278 _color_shuffle = _slot_data["shuffle_colors"] 301 _color_shuffle = _slot_data["shuffle_colors"]
279 302
280 if _slot_data.has("shuffle_doors"): 303 if _slot_data.has("shuffle_doors"):
281 _door_shuffle = (_slot_data["shuffle_doors"] > 0) 304 if _slot_data.has("group_doors"):
305 _door_shuffle = (_slot_data["shuffle_doors"] == 2)
306 _panel_door_shuffle = (_slot_data["shuffle_doors"] == 1)
307 else:
308 _door_shuffle = (_slot_data["shuffle_doors"] > 0)
309 _panel_door_shuffle = false
282 else: 310 else:
283 _door_shuffle = false 311 _door_shuffle = false
312 _panel_door_shuffle = false
284 313
285 if _slot_data.has("shuffle_paintings"): 314 if _slot_data.has("shuffle_paintings"):
286 _painting_shuffle = _slot_data["shuffle_paintings"] 315 _painting_shuffle = _slot_data["shuffle_paintings"]
@@ -391,17 +420,33 @@ func _on_data():
391 420
392 requestSync() 421 requestSync()
393 422
394 sendMessage( 423 _hints_key = "_read_hints_%d_%d" % [_team, _slot]
395 [ 424
396 { 425 var cmds = [
397 "cmd": "Set", 426 {
398 "key": "Lingo_%d_Paintings" % [_slot], 427 "cmd": "Set",
399 "default": [], 428 "key": "Lingo_%d_Paintings" % [_slot],
400 "want_reply": true, 429 "default": [],
401 "operations": [{"operation": "default", "value": []}] 430 "want_reply": true,
402 } 431 "operations": [{"operation": "default", "value": []}]
403 ] 432 },
404 ) 433 {"cmd": "Get", "keys": [_hints_key]}
434 ]
435
436 var set_notifies = [_hints_key]
437 for k in range(0, kPANEL_BITFIELDS):
438 var panel_key = "Lingo_%d_Panels_%d" % [_slot, k]
439 set_notifies.append(panel_key)
440 cmds.append({
441 "cmd": "Set",
442 "key": panel_key,
443 "default": 0,
444 "operations": [{"operation": "default", "value": 0}]
445 })
446
447 cmds.append({"cmd": "SetNotify", "keys": set_notifies})
448
449 sendMessage(cmds)
405 450
406 emit_signal("client_connected") 451 emit_signal("client_connected")
407 452
@@ -500,9 +545,12 @@ func _on_data():
500 ) 545 )
501 else: 546 else:
502 if message["receiving"] != _slot: 547 if message["receiving"] != _slot:
503 messages.showMessage( 548 var sentMsg = (
504 "Sent [color=%s]%s[/color] to %s" % [item_color, item_name, player_name] 549 "Sent [color=%s]%s[/color] to %s" % [item_color, item_name, player_name]
505 ) 550 )
551 if _hinted_locations.has(message["item"]["location"]):
552 sentMsg += " ([color=#fafad2]Hinted![/color])"
553 messages.showMessage(sentMsg)
506 554
507 elif cmd == "Bounced": 555 elif cmd == "Bounced":
508 if ( 556 if (
@@ -523,8 +571,22 @@ func _on_data():
523 get_tree().get_root().get_node("Spatial/player/pause_menu")._reload() 571 get_tree().get_root().get_node("Spatial/player/pause_menu")._reload()
524 572
525 elif cmd == "SetReply": 573 elif cmd == "SetReply":
526 if message.has("key") and message["key"] == ("Lingo_%d_Paintings" % _slot): 574 if message.has("key"):
527 _checked_paintings = message["value"] 575 if message["key"] == ("Lingo_%d_Paintings" % _slot):
576 _checked_paintings = message["value"]
577 elif message["key"] == _hints_key:
578 parseHints(message["value"])
579 elif message["key"].begins_with("Lingo_%d_Panels_" % _slot):
580 var key_index = int(message["key"].substr(("Lingo_%d_Panels_" % _slot).length()))
581 var field_value = int(message["value"])
582 for k in range(0, kPANEL_BITFIELD_LENGTH):
583 if field_value & (1 << k) != 0:
584 var panel_index = key_index * kPANEL_BITFIELD_LENGTH + k
585 syncSolvedPanel(panel_index)
586
587 elif cmd == "Retrieved":
588 if message.has("keys") and message["keys"].has(_hints_key):
589 parseHints(message["keys"][_hints_key])
528 590
529 591
530func _process(_delta): 592func _process(_delta):
@@ -715,14 +777,53 @@ func mapFinishedLoading():
715 _has_colors = ["white"] 777 _has_colors = ["white"]
716 emit_signal("evaluate_solvability") 778 emit_signal("evaluate_solvability")
717 779
780 messages.showMessage("Press TAB to open the text client.")
781
782 _held_messages.clear()
783 _batch_messages = true
718 for item in _held_items: 784 for item in _held_items:
719 processItem(item["item"], item["index"], item["from"], item["flags"]) 785 processItem(item["item"], item["index"], item["from"], item["flags"])
786 _batch_messages = false
787
788 for item_name in _held_messages:
789 if _held_messages[item_name][1].size() > 1:
790 messages.showMessage(
791 (
792 "Received [color=%s]%s[/color] (x%d)"
793 % [
794 _held_messages[item_name][0],
795 item_name,
796 _held_messages[item_name][1].size()
797 ]
798 )
799 )
800 else:
801 messages.showMessage(_held_messages[item_name][1][0])
720 802
721 sendMessage([{"cmd": "LocationChecks", "locations": _held_locations}]) 803 sendMessage([{"cmd": "LocationChecks", "locations": _held_locations}])
722 804
805 var panel_data = []
806 for k in range(0, kPANEL_BITFIELDS):
807 panel_data.append(0)
808
809 for panel_index in _held_panels:
810 var key_index = panel_index / kPANEL_BITFIELD_LENGTH
811 var field_slot = panel_index % kPANEL_BITFIELD_LENGTH
812 panel_data[key_index] = panel_data[key_index] | (1 << field_slot)
813
814 for k in range(0, kPANEL_BITFIELDS):
815 if panel_data[k] != 0:
816 setValue("Panels_%d" % k, panel_data[k], "or")
817
723 _map_loaded = true 818 _map_loaded = true
819
820 for synced in _held_synced_panels:
821 syncSolvedPanel(synced)
822
724 _held_items = [] 823 _held_items = []
725 _held_locations = [] 824 _held_locations = []
825 _held_panels = []
826 _held_synced_panels = []
726 827
727 828
728func processItem(item, index, from, flags): 829func processItem(item, index, from, flags):
@@ -745,6 +846,18 @@ func processItem(item, index, from, flags):
745 for door_id in gamedata.door_ids_by_item_id[int(item)]: 846 for door_id in gamedata.door_ids_by_item_id[int(item)]:
746 doorsNode.get_node(door_id).openDoor() 847 doorsNode.get_node(door_id).openDoor()
747 848
849 if gamedata.panel_ids_by_item_id.has(int(item)):
850 var panel_ids = gamedata.panel_ids_by_item_id[int(item)]
851 if wasGeneratedOnVersion(0, 5, 1):
852 var extradata = get_node("Extradata")
853 if extradata.panels_mode_051_panel_fixes.has(int(item)):
854 panel_ids = extradata.panels_mode_051_panel_fixes[int(item)]
855
856 var panelsNode = get_tree().get_root().get_node("Spatial/Panels")
857 for panel_id in panel_ids:
858 panelsNode.get_node(panel_id).get_node("AP_Panel").locked = false
859 emit_signal("evaluate_solvability")
860
748 if gamedata.painting_ids_by_item_id.has(int(item)): 861 if gamedata.painting_ids_by_item_id.has(int(item)):
749 var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings") 862 var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings")
750 var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings") 863 var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings")
@@ -765,19 +878,38 @@ func processItem(item, index, from, flags):
765 warpsNode.get_node(warp_id).unlock_warp() 878 warpsNode.get_node(warp_id).unlock_warp()
766 879
767 # Handle progressive items. 880 # Handle progressive items.
768 if int(item) in gamedata.items_by_progressive_id.keys(): 881 var is_progressive_door = int(item) in gamedata.door_items_by_progressive_id
882 var is_progressive_panel = int(item) in gamedata.panel_items_by_progressive_id
883 var progitems = null
884 var prognames = null
885
886 if is_progressive_door and is_progressive_panel:
887 if _door_shuffle:
888 progitems = gamedata.door_items_by_progressive_id[int(item)]
889 prognames = door_progressive_items
890 else:
891 progitems = gamedata.panel_items_by_progressive_id[int(item)]
892 prognames = panel_progressive_items
893 elif is_progressive_door:
894 progitems = gamedata.door_items_by_progressive_id[int(item)]
895 prognames = door_progressive_items
896 elif is_progressive_panel:
897 progitems = gamedata.panel_items_by_progressive_id[int(item)]
898 prognames = panel_progressive_items
899
900 if progitems != null:
769 if not int(item) in _progressive_progress: 901 if not int(item) in _progressive_progress:
770 _progressive_progress[int(item)] = 0 902 _progressive_progress[int(item)] = 0
771 903
772 if _progressive_progress[int(item)] < gamedata.items_by_progressive_id[int(item)].size(): 904 if _progressive_progress[int(item)] < progitems.size():
773 var subitems = gamedata.items_by_progressive_id[int(item)] 905 var subitem_id = progitems[_progressive_progress[int(item)]]
774 var subitem_id = subitems[_progressive_progress[int(item)]]
775 global._print("Subitem: %d" % subitem_id) 906 global._print("Subitem: %d" % subitem_id)
776 processItem(subitem_id, null, null, null) 907 processItem(subitem_id, null, null, null)
908 item_name += " (%s)" % prognames[item_name][_progressive_progress[int(item)]]
777 _progressive_progress[int(item)] += 1 909 _progressive_progress[int(item)] += 1
778 910
779 if _color_shuffle and color_items.has(_item_id_to_name["Lingo"][item]): 911 if _color_shuffle and color_items.has(_item_id_to_name["Lingo"][float(item)]):
780 var lcol = _item_id_to_name["Lingo"][item].to_lower() 912 var lcol = _item_id_to_name["Lingo"][float(item)].to_lower()
781 if not _has_colors.has(lcol): 913 if not _has_colors.has(lcol):
782 _has_colors.append(lcol) 914 _has_colors.append(lcol)
783 emit_signal("evaluate_solvability") 915 emit_signal("evaluate_solvability")
@@ -787,31 +919,36 @@ func processItem(item, index, from, flags):
787 _last_new_item = index 919 _last_new_item = index
788 saveLocaldata() 920 saveLocaldata()
789 921
790 if item_name in progressive_items:
791 var subitem = progressive_items[item_name][_progressive_progress[int(item)] - 1]
792 item_name += " (%s)" % subitem
793
794 var player_name = "Unknown" 922 var player_name = "Unknown"
795 if _player_name_by_slot.has(from): 923 if _player_name_by_slot.has(from):
796 player_name = _player_name_by_slot[from] 924 player_name = _player_name_by_slot[from]
797 925
798 var item_color = colorForItemType(flags) 926 var item_color = colorForItemType(flags)
799 927
928 var message
800 if from == _slot: 929 if from == _slot:
801 messages.showMessage("Found [color=%s]%s[/color]" % [item_color, item_name]) 930 message = "Found [color=%s]%s[/color]" % [item_color, item_name]
802 else: 931 else:
803 messages.showMessage( 932 message = "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name]
804 "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name] 933
805 ) 934 global._print(message)
935
936 if _batch_messages:
937 if _held_messages.has(item_name):
938 _held_messages[item_name][1].append(message)
939 else:
940 _held_messages[item_name] = [item_color, [message]]
941 else:
942 messages.showMessage(message)
806 943
807 var effects_node = get_tree().get_root().get_node("Spatial/AP_Effects") 944 var effects_node = get_tree().get_root().get_node("Spatial/AP_Effects")
808 if item_name == "Slowness Trap": 945 if item_name == "Slowness Trap" and !_speed_boost_mode:
809 effects_node.trigger_slowness_trap() 946 effects_node.trigger_slowness_trap()
810 if item_name == "Iceland Trap": 947 if item_name == "Iceland Trap":
811 effects_node.trigger_iceland_trap() 948 effects_node.trigger_iceland_trap()
812 if item_name == "Atbash Trap": 949 if item_name == "Atbash Trap":
813 effects_node.trigger_atbash_trap() 950 effects_node.trigger_atbash_trap()
814 if item_name == "Speed Boost": 951 if item_name == "Speed Boost" and _speed_boost_mode:
815 effects_node.trigger_speed_boost() 952 effects_node.trigger_speed_boost()
816 if item_name == "Puzzle Skip": 953 if item_name == "Puzzle Skip":
817 _puzzle_skips += 1 954 _puzzle_skips += 1
@@ -819,6 +956,24 @@ func processItem(item, index, from, flags):
819 saveLocaldata() 956 saveLocaldata()
820 957
821 958
959func syncSolvedPanel(panel_index):
960 if _solved_panels.has(panel_index):
961 return
962
963 if _map_loaded:
964 var panel_name = _panelsBySolveIndex[panel_index]
965 global._print("Synced solved panel %d: %s" % [panel_index, panel_name])
966
967 if !panel_name.begins_with("EndPanel"):
968 var the_panel = get_tree().get_root().get_node("Spatial/Panels").get_node(panel_name)
969
970 if !the_panel.is_complete:
971 the_panel.get_node("AP_Panel").solved_remotely = true
972 the_panel.get_node("Viewport/GUI/Panel/TextEdit").complete()
973 else:
974 _held_synced_panels.append(panel_index)
975
976
822func doorIsVanilla(door): 977func doorIsVanilla(door):
823 return !$Gamedata.mentioned_doors.has(door) 978 return !$Gamedata.mentioned_doors.has(door)
824 979
@@ -863,10 +1018,35 @@ func checkPainting(painting_id):
863 setValue("Paintings", [painting_id], "add") 1018 setValue("Paintings", [painting_id], "add")
864 1019
865 1020
1021func solvePanel(panel_index, solved_remotely=false):
1022 _solved_panels.append(panel_index)
1023
1024 if solved_remotely:
1025 return
1026
1027 if _map_loaded:
1028 var key_index = panel_index / kPANEL_BITFIELD_LENGTH
1029 var field_slot = panel_index % kPANEL_BITFIELD_LENGTH
1030 setValue("Panels_%d" % key_index, 1 << field_slot, "or")
1031 else:
1032 _held_panels.append(panel_index)
1033
1034
1035func parseHints(hints):
1036 _hinted_locations.clear()
1037
1038 for hint in hints:
1039 if hint["finding_player"] == int(_slot):
1040 _hinted_locations.append(hint["location"])
1041
1042
866func colorForItemType(flags): 1043func colorForItemType(flags):
867 var int_flags = int(flags) 1044 var int_flags = int(flags)
868 if int_flags & 1: # progression 1045 if int_flags & 1: # progression
869 return "#bc51e0" 1046 if int_flags & 2: # proguseful
1047 return "#f0d200"
1048 else:
1049 return "#bc51e0"
870 elif int_flags & 2: # useful 1050 elif int_flags & 2: # useful
871 return "#2b67ff" 1051 return "#2b67ff"
872 elif int_flags & 4: # trap 1052 elif int_flags & 4: # trap
@@ -928,3 +1108,11 @@ func compareVersion(lhs, rhs):
928 1108
929func wasGeneratedBeforeVersion(major, minor, build): 1109func wasGeneratedBeforeVersion(major, minor, build):
930 return compareVersion(_gen_version, {"major": major, "minor": minor, "build": build}) 1110 return compareVersion(_gen_version, {"major": major, "minor": minor, "build": build})
1111
1112
1113func wasGeneratedOnVersion(major, minor, build):
1114 return (
1115 _gen_version["major"] == major
1116 and _gen_version["minor"] == minor
1117 and _gen_version["build"] == build
1118 )
diff --git a/Archipelago/extradata.gd b/Archipelago/extradata.gd index 89c41d2..c90433a 100644 --- a/Archipelago/extradata.gd +++ b/Archipelago/extradata.gd
@@ -93,3 +93,102 @@ 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
97var 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
164var 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}
180
181var pilgrimage_061_painting_fixes = [
182 "outside2",
183 "outside_2",
184 "outside_3",
185 "outside_4",
186 "north2",
187 "south2",
188 "west2",
189 "east_enter",
190 "owl_painting_4",
191 "garden_painting",
192 "yinyang_painting",
193 "fruitbowl_painting2"
194]
diff --git a/Archipelago/load.gd b/Archipelago/load.gd index 09aaee2..b95e4c4 100644 --- a/Archipelago/load.gd +++ b/Archipelago/load.gd
@@ -120,6 +120,7 @@ func _load():
120 new_master_cdp.translation = old_master_cdp.translation 120 new_master_cdp.translation = old_master_cdp.translation
121 new_master_cdp.rotation = old_master_cdp.rotation 121 new_master_cdp.rotation = old_master_cdp.rotation
122 get_node("CountdownPanels").add_child(new_master_cdp) 122 get_node("CountdownPanels").add_child(new_master_cdp)
123 old_master_cdp.total = 5000
123 old_master_cdp.queue_free() 124 old_master_cdp.queue_free()
124 125
125 # Configure AN OTHER WAY. 126 # Configure AN OTHER WAY.
@@ -513,23 +514,6 @@ func _load():
513 fearless_door.name = "Door_hider_new1" 514 fearless_door.name = "Door_hider_new1"
514 fearless_door.translation.y = 5 515 fearless_door.translation.y = 5
515 get_node("Doors/Naps Room Doors").add_child(fearless_door) 516 get_node("Doors/Naps Room Doors").add_child(fearless_door)
516
517 # Set up notifiers for each achievement panel, for the tracker.
518 var notifier_script = apclient.SCRIPT_notifier
519 for panel in gamedata.panels:
520 if "achievement" in panel:
521 var panel_node = panels_parent.get_node(panel["id"])
522 var script_instance = notifier_script.new()
523 script_instance.name = "Achievement_Notifier"
524 script_instance.key = "Achievement|%s" % panel["achievement"]
525 panel_node.add_child(script_instance)
526
527 if "hunt" in panel and panel["hunt"] and not (gamedata.classification_by_location_id[panel["loc"]] & apclient._location_classification_bit):
528 var panel_node = panels_parent.get_node(panel["id"])
529 var script_instance = notifier_script.new()
530 script_instance.name = "Hunt_Notifier"
531 script_instance.key = "Hunt|%d" % panel["loc"]
532 panel_node.add_child(script_instance)
533 517
534 # Make stack/double puzzles into proxies, unless panelsanity is on. 518 # Make stack/double puzzles into proxies, unless panelsanity is on.
535 if apclient._location_classification_bit != apclient.kCLASSIFICATION_LOCAL_INSANITY: 519 if apclient._location_classification_bit != apclient.kCLASSIFICATION_LOCAL_INSANITY:
@@ -551,6 +535,20 @@ func _load():
551 proxynode.exact_proxy = true 535 proxynode.exact_proxy = true
552 proxynode.request_ready() 536 proxynode.request_ready()
553 oldparent.add_child(proxynode) 537 oldparent.add_child(proxynode)
538
539 # If the world was generated on 0.5.1, apply the hotfix for the number hunt doors.
540 if apclient._panel_door_shuffle && apclient.wasGeneratedOnVersion(0, 5, 1):
541 var number_hunt_parent = get_node("Doors/Count Up Room Area Doors")
542 var extradata_051_fix = apclient.get_node("Extradata").panels_mode_051_door_fixes
543 for template_door_path in extradata_051_fix:
544 var template_door = number_hunt_parent.get_node(template_door_path)
545 var impacted_doors = extradata_051_fix[template_door_path]
546 for impacted_door_path in impacted_doors:
547 var impacted_door = number_hunt_parent.get_node(impacted_door_path)
548 var copied_door = impacted_door.duplicate()
549 copied_door.panels = template_door.panels
550 number_hunt_parent.add_child(copied_door)
551 impacted_door.queue_free()
554 552
555 # Attach a script to every panel so that we can do things like conditionally 553 # Attach a script to every panel so that we can do things like conditionally
556 # disable them. 554 # disable them.
@@ -564,9 +562,14 @@ func _load():
564 var script_instance = panel_script.new() 562 var script_instance = panel_script.new()
565 script_instance.name = "AP_Panel" 563 script_instance.name = "AP_Panel"
566 script_instance.data = panel 564 script_instance.data = panel
565 script_instance.solve_index = panel["solve_index"]
566 if apclient._panel_door_shuffle and gamedata.mentioned_panels.has(panel["id"]):
567 script_instance.locked = true
567 panel_node.add_child(script_instance) 568 panel_node.add_child(script_instance)
568 apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability") 569 apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability")
569 570
571 apclient._panelsBySolveIndex[panel["solve_index"]] = panel["id"]
572
570 # Hook up the goal panel. 573 # Hook up the goal panel.
571 if apclient._victory_condition == apclient.kTHE_MASTER: 574 if apclient._victory_condition == apclient.kTHE_MASTER:
572 var the_master = self.get_node("Panels/Countdown Panels/Panel_master_master") 575 var the_master = self.get_node("Panels/Countdown Panels/Panel_master_master")
@@ -676,6 +679,12 @@ func _load():
676 multiplayer_node.ghost_mode = true 679 multiplayer_node.ghost_mode = true
677 add_child(multiplayer_node) 680 add_child(multiplayer_node)
678 681
682 # Create the autotracker node.
683 var autotracker_script = apclient.SCRIPT_tracker
684 var autotracker = autotracker_script.new()
685 autotracker.set_name("AP_Tracker")
686 self.add_child(autotracker)
687
679 # Hook up Geronimo handler. 688 # Hook up Geronimo handler.
680 $player.connect("player_jumped", apclient, "geronimo") 689 $player.connect("player_jumped", apclient, "geronimo")
681 690
diff --git a/Archipelago/multiplayer.gd b/Archipelago/multiplayer.gd index c2d9875..f2e4175 100644 --- a/Archipelago/multiplayer.gd +++ b/Archipelago/multiplayer.gd
@@ -43,7 +43,7 @@ func _update_lobby_members():
43 var member_id: int = Steam.getLobbyMemberByIndex(active_lobby_id, i) 43 var member_id: int = Steam.getLobbyMemberByIndex(active_lobby_id, i)
44 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:
45 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")
46 active_lobby_members[member_id].steam_name = slot_name 46 active_lobby_members[member_id].steam_name = "%s\n%s" % [slot_name, Steam.getFriendPersonaName(member_id)]
47 47
48 48
49func say(text): 49func say(text):
diff --git a/Archipelago/notifier.gd b/Archipelago/notifier.gd deleted file mode 100644 index 57d6564..0000000 --- a/Archipelago/notifier.gd +++ /dev/null
@@ -1,14 +0,0 @@
1extends Node
2
3var key
4
5
6func _ready():
7 self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").connect(
8 "answer_correct", self, "handle_correct"
9 )
10
11
12func handle_correct():
13 var apclient = global.get_node("Archipelago")
14 apclient.setValue(key, true)
diff --git a/Archipelago/painting.gd b/Archipelago/painting.gd index adc8337..dc791ce 100644 --- a/Archipelago/painting.gd +++ b/Archipelago/painting.gd
@@ -1,10 +1,26 @@
1extends "res://scripts/painting.gd" 1extends "res://scripts/painting.gd"
2 2
3func _looked_at(var body, var painting): 3var breaks_pilgrimage = false
4 ._looked_at(body, painting) 4
5 5
6 if body.is_in_group("player") && (painting.get_name() == self.get_name()): 6func _ready():
7 var apclient = global.get_node("Archipelago") 7 var apclient = global.get_node("Archipelago")
8 if !apclient._pilgrimage_allows_paintings: 8 if !apclient._pilgrimage_allows_paintings:
9 global.sunwarp = 1 9 if apclient.wasGeneratedBeforeVersion(0, 6, 2):
10 body.get_node("pivot/camera/sunwarp_background").visible = false 10 var extradata = apclient.get_node("Extradata")
11 if not extradata.pilgrimage_061_painting_fixes.has(get_name()):
12 breaks_pilgrimage = true
13 else:
14 breaks_pilgrimage = true
15
16
17func _looked_at(body, painting):
18 ._looked_at(body, painting)
19
20 if (
21 breaks_pilgrimage
22 and body.is_in_group("player")
23 and (painting.get_name() == self.get_name())
24 ):
25 global.sunwarp = 1
26 body.get_node("pivot/camera/sunwarp_background").visible = false
diff --git a/Archipelago/panel.gd b/Archipelago/panel.gd index fc5963a..2224ffa 100644 --- a/Archipelago/panel.gd +++ b/Archipelago/panel.gd
@@ -5,6 +5,9 @@ var orig_text = ""
5var atbash_text = "" 5var atbash_text = ""
6var orig_color = Color(0, 0, 0, 0) 6var orig_color = Color(0, 0, 0, 0)
7var solvable = true 7var solvable = true
8var locked = false
9var solve_index = null
10var solved_remotely = false
8 11
9const kAtbashPre = "abcdefghijklmnopqrstuvwxyz1234567890+-" 12const kAtbashPre = "abcdefghijklmnopqrstuvwxyz1234567890+-"
10const kAtbashPost = "zyxwvutsrqponmlkjihgfedcba0987654321-+" 13const kAtbashPost = "zyxwvutsrqponmlkjihgfedcba0987654321-+"
@@ -31,6 +34,10 @@ func answer_correct():
31 var effects = get_tree().get_root().get_node("Spatial/AP_Effects") 34 var effects = get_tree().get_root().get_node("Spatial/AP_Effects")
32 effects.deactivate_atbash_trap() 35 effects.deactivate_atbash_trap()
33 36
37 if solve_index != null:
38 var apclient = global.get_node("Archipelago")
39 apclient.solvePanel(solve_index, solved_remotely)
40
34 41
35func evaluate_solvability(): 42func evaluate_solvability():
36 var apclient = global.get_node("Archipelago") 43 var apclient = global.get_node("Archipelago")
@@ -39,7 +46,9 @@ func evaluate_solvability():
39 solvable = true 46 solvable = true
40 var missing = [] 47 var missing = []
41 48
42 if apclient._color_shuffle: 49 if locked:
50 solvable = false
51 elif apclient._color_shuffle:
43 for color in data["color"]: 52 for color in data["color"]:
44 if not apclient._has_colors.has(color): 53 if not apclient._has_colors.has(color):
45 missing.append(color) 54 missing.append(color)
@@ -52,6 +61,12 @@ func evaluate_solvability():
52 self.get_parent().get_node("Viewport/GUI/Panel/Label").text = orig_text 61 self.get_parent().get_node("Viewport/GUI/Panel/Label").text = orig_text
53 self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = true 62 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 63 self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = orig_color
64 elif locked:
65 self.get_parent().get_node("Viewport/GUI/Panel/Label").text = "Locked"
66 self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = false
67 self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = Color(
68 0.2, 0.7, 0.7
69 )
55 else: 70 else:
56 var missing_text = "Missing: " 71 var missing_text = "Missing: "
57 for thing in missing: 72 for thing in missing:
diff --git a/Archipelago/player.gd b/Archipelago/player.gd index 52d743a..6638329 100644 --- a/Archipelago/player.gd +++ b/Archipelago/player.gd
@@ -2,12 +2,16 @@ extends "res://scripts/player.gd"
2 2
3 3
4var _oldpos = Vector3(0, -200, 0) 4var _oldpos = Vector3(0, -200, 0)
5var _oldpos_fine = Vector3(0, -200, 0)
5 6
6 7
7func _ready(): 8func _ready():
8 _oldpos = translation 9 _oldpos = translation
9 _oldpos.y = 0 10 _oldpos.y = 0
10 11
12 _oldpos_fine = translation
13 _oldpos_fine.y = 0
14
11 var apclient = global.get_node("Archipelago") 15 var apclient = global.get_node("Archipelago")
12 if apclient.track_player: 16 if apclient.track_player:
13 var tracking_timer = Timer.new() 17 var tracking_timer = Timer.new()
@@ -17,8 +21,19 @@ func _ready():
17 tracking_timer.connect("timeout", self, "_tick_tracking") 21 tracking_timer.connect("timeout", self, "_tick_tracking")
18 tracking_timer.start() 22 tracking_timer.start()
19 23
24 var tracking_timer_fine = Timer.new()
25 tracking_timer_fine.name = "TrackingTimerFine"
26 tracking_timer_fine.wait_time = 0.5
27 add_child(tracking_timer_fine)
28 tracking_timer_fine.connect("timeout", self, "_tick_tracking_fine")
29 tracking_timer_fine.start()
30
20 31
21func _tick_tracking(): 32func _tick_tracking():
33 var tracker = get_tree().get_root().get_node("Spatial/AP_Tracker")
34 if tracker.has_connection():
35 return
36
22 var newpos = translation 37 var newpos = translation
23 newpos.y = 0 38 newpos.y = 0
24 39
@@ -29,6 +44,20 @@ func _tick_tracking():
29 apclient.setValue("PlayerPos", {"x": int(_oldpos.x), "z": int(_oldpos.z)}) 44 apclient.setValue("PlayerPos", {"x": int(_oldpos.x), "z": int(_oldpos.z)})
30 45
31 46
47func _tick_tracking_fine():
48 var tracker = get_tree().get_root().get_node("Spatial/AP_Tracker")
49 if !tracker.has_connection():
50 return
51
52 var newpos = translation
53 newpos.y = 0
54
55 if newpos != _oldpos_fine && newpos.distance_to(_oldpos_fine) > 0.5:
56 _oldpos_fine = newpos
57
58 tracker.update_position(int(_oldpos_fine.x), int(_oldpos_fine.z))
59
60
32func _solving(): 61func _solving():
33 ._solving() 62 ._solving()
34 63
diff --git a/Archipelago/settings_screen.gd b/Archipelago/settings_screen.gd index 2ed8594..f390eed 100644 --- a/Archipelago/settings_screen.gd +++ b/Archipelago/settings_screen.gd
@@ -28,11 +28,11 @@ func _ready():
28 apclient_instance.SCRIPT_location = load("user://maps/Archipelago/location.gd") 28 apclient_instance.SCRIPT_location = load("user://maps/Archipelago/location.gd")
29 apclient_instance.SCRIPT_multiplayer = load("user://maps/Archipelago/multiplayer.gd") 29 apclient_instance.SCRIPT_multiplayer = load("user://maps/Archipelago/multiplayer.gd")
30 apclient_instance.SCRIPT_mypainting = load("user://maps/Archipelago/mypainting.gd") 30 apclient_instance.SCRIPT_mypainting = load("user://maps/Archipelago/mypainting.gd")
31 apclient_instance.SCRIPT_notifier = load("user://maps/Archipelago/notifier.gd")
32 apclient_instance.SCRIPT_panel = load("user://maps/Archipelago/panel.gd") 31 apclient_instance.SCRIPT_panel = load("user://maps/Archipelago/panel.gd")
33 var pilg_term = load("user://maps/Archipelago/pilgrimage_terminator.gd") 32 var pilg_term = load("user://maps/Archipelago/pilgrimage_terminator.gd")
34 apclient_instance.SCRIPT_pilgrimage_terminator = pilg_term 33 apclient_instance.SCRIPT_pilgrimage_terminator = pilg_term
35 apclient_instance.SCRIPT_textclient = load("user://maps/Archipelago/textclient.gd") 34 apclient_instance.SCRIPT_textclient = load("user://maps/Archipelago/textclient.gd")
35 apclient_instance.SCRIPT_tracker = load("user://maps/Archipelago/tracker.gd")
36 apclient_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd") 36 apclient_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd")
37 37
38 var apdata = ResourceLoader.load("user://maps/Archipelago/gamedata.gd") 38 var apdata = ResourceLoader.load("user://maps/Archipelago/gamedata.gd")
diff --git a/Archipelago/textclient.gd b/Archipelago/textclient.gd index f100776..3abd9e0 100644 --- a/Archipelago/textclient.gd +++ b/Archipelago/textclient.gd
@@ -25,6 +25,7 @@ func _ready():
25 label.margin_top = 80 25 label.margin_top = 80
26 label.margin_bottom = 720 26 label.margin_bottom = 720
27 label.scroll_following = true 27 label.scroll_following = true
28 label.selection_enabled = true
28 panel.add_child(label) 29 panel.add_child(label)
29 30
30 var dynamic_font = DynamicFont.new() 31 var dynamic_font = DynamicFont.new()
@@ -51,7 +52,7 @@ func _ready():
51 52
52func _input(event): 53func _input(event):
53 if event is InputEventKey and event.pressed: 54 if event is InputEventKey and event.pressed:
54 if event.scancode == KEY_TAB: 55 if event.scancode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
55 if !get_tree().paused: 56 if !get_tree().paused:
56 is_open = true 57 is_open = true
57 get_tree().paused = true 58 get_tree().paused = true
diff --git a/Archipelago/tracker.gd b/Archipelago/tracker.gd new file mode 100644 index 0000000..9def744 --- /dev/null +++ b/Archipelago/tracker.gd
@@ -0,0 +1,89 @@
1extends Node
2
3var autotracker_port = 41253
4
5var _server = WebSocketServer.new()
6var _peers = []
7
8
9func _ready():
10 _server.bind_ip = "127.0.0.1"
11 _server.connect("client_connected", self, "_connection_established")
12 _server.connect("client_disconnected", self, "_connection_dropped")
13 _server.connect("data_received", self, "_data_received")
14 _server.listen(autotracker_port)
15
16
17func _process(_delta):
18 _server.poll()
19
20
21func _connection_established(id, _protocol):
22 _peers.append(id)
23
24 var apclient = global.get_node("Archipelago")
25
26 var msg = {
27 "cmd": "Connect",
28 "slot":
29 {"server": apclient.ap_server, "player": apclient.ap_user, "password": apclient.ap_pass}
30 }
31
32 var peer = _server.get_peer(id)
33 peer.set_write_mode(WebSocketPeer.WRITE_MODE_TEXT)
34 peer.put_packet(JSON.print(msg).to_utf8())
35
36
37func _connection_dropped(id, _was_clean_close):
38 _peers.erase(id)
39
40
41func _data_received(id):
42 var peer = _server.get_peer(id)
43 peer.set_write_mode(WebSocketPeer.WRITE_MODE_TEXT)
44
45 var packet_text = peer.get_packet().get_string_from_utf8()
46 global._print("Got data from tracker: " + packet_text)
47 var data = JSON.parse(packet_text)
48 if data.error != OK:
49 global._print("Error parsing packet from Tracker: " + data.error_string)
50 return
51
52 var apclient = global.get_node("Archipelago")
53 var msg = data.result
54
55 if msg["cmd"] == "Sync":
56 var resp = {}
57
58 if apclient.track_player:
59 var player = get_tree().get_root().get_node("Spatial/player")
60 resp = {
61 "cmd": "UpdatePosition",
62 "position": {"x": int(player._oldpos_fine.x), "z": int(player._oldpos_fine.z)}
63 }
64
65 peer.put_packet(JSON.print(resp).to_utf8())
66
67
68func _broadcast(msg):
69 var to_remove = []
70 var serialized = JSON.print(msg).to_utf8()
71
72 for peer_id in _peers:
73 if _server.has_peer(peer_id):
74 var peer = _server.get_peer(peer_id)
75 peer.set_write_mode(WebSocketPeer.WRITE_MODE_TEXT)
76 peer.put_packet(serialized)
77 else:
78 to_remove.append(peer_id)
79
80 for peer_id in to_remove:
81 _peers.erase(peer_id)
82
83
84func has_connection():
85 return _peers.size() > 0
86
87
88func update_position(x, z):
89 _broadcast({"cmd": "UpdatePosition", "position": {"x": x, "z": z}})
diff --git a/CHANGELOG.md b/CHANGELOG.md index d00999c..6e02284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,142 @@
1# lingo-archipelago Releases 1# lingo-archipelago Releases
2 2
3## v5.4.0 - 2025-05-17
4
5- ProgUseful items (items marked both Progression and Useful) are now colored
6 gold in the text client and the message popups.
7- Fixed an issue where two copies of THE MASTER could appear on top of one
8 another.
9- This update adds a workaround for
10 [a logic error in Archipelago 0.6.1 and earlier](https://github.com/ArchipelagoMW/Archipelago/pull/5005).
11 - This bug causes the paintings in The Bearer to be considered logically
12 usable for the pilgrimage even when the "Allow Paintings For Pilgrimage"
13 option is disabled, despite the paintings not being usable in-game. This
14 can, on rare occasion, cause the game to become unbeatable.
15 - If you are playing in a slot affected by this issue (generated using
16 Archipelago 0.6.1 or earlier, with pilgrimage enabled and paintings during
17 pilgrimage disabled), the mod will work around the issue by allowing you to
18 use the paintings in The Bearer without breaking your pilgrimage.
19 - This can lead to non-intuitive gameplay, as one would not expect the
20 paintings to be usable in this situation. You can use the Lingo AP Tracker
21 to figure out whether or not you are expected to be able to perform a
22 pilgrimage, as it currently uses the same broken logic as the generator.
23 - There is already a fix for the logic error, and it will likely be included
24 as part of the next major Archipelago release. I apologize for the
25 inconvenience.
26
27Download:
28[lingo-archipelago-v5.4.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.4.0.zip)<br/>
29Source: [v5.4.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.4.0)
30
31## v5.3.1 - 2025-03-22
32
33- Fixed a bug with panel syncing that would cause the client to crash on
34 startup.
35
36Download:
37[lingo-archipelago-v5.3.1.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.3.1.zip)<br/>
38Source: [v5.3.1](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.3.1)
39
40## v5.3.0 - 2025-03-21
41
42- Panel solves are now saved in the multiworld state.
43 - This means you no longer have to copy your save file if you want to play on
44 a second computer. Connecting to the slot will automatically download all of
45 your solved panels.
46 - It also makes slot co-op easy and seamless. Panels solved by other players
47 connected to the same slot as you will automatically solve for you, and any
48 doors connected to them will open.
49- In multiplayer mode, players' Steam names will now appear over their heads, so
50 that multiple players connected to the same slot can be distinguished.
51
52Download:
53[lingo-archipelago-v5.3.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.3.0.zip)<br/>
54Source: [v5.3.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.3.0)
55
56## v5.2.0 - 2025-02-10
57
58- When displaying newly received items at connection time, multiple instances of
59 the same received item will be consolidated to save space.
60
61Download:
62[lingo-archipelago-v5.2.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.2.0.zip)<br/>
63Source: [v5.2.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.2.0)
64
65## v5.1.0 - 2024-12-20
66
67- The tracker can now connect to Lingo when it is running the Archipelago mod,
68 which allows it to display the player's position and solved panels. This
69 requires v0.12.0 of the tracker.
70
71Download:
72[lingo-archipelago-v5.1.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.1.0.zip)<br/>
73Source: [v5.1.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.1.0)
74
75## v5.0.1 - 2024-12-08
76
77- This update adds a workaround for
78 [a logic error in Archipelago 0.5.1](https://github.com/ArchipelagoMW/Archipelago/pull/4342).
79 If your slot is affected by this bug (if you are playing panels mode door
80 shuffle in a multiworld generated on Archipelago 0.5.1), the following changes
81 will occur:
82 - The broken panels (listed at the above link) will now unlock at one
83 Progressive Number Hunt earlier than would be expected.
84 - The doors covering the broken panels will now open when all of the numbers
85 in the previous set have been solved, instead of when the number in the
86 number hunt hallway is solved.
87- This can lead to some non-intuitive gameplay, because the panels no longer
88 unlock the way they do in the base game. To help with this, v0.11.5 of the
89 tracker now uses the 0.5.1 broken logic, so that you can always tell what is
90 expected of you.
91- There is already a fix for the logic error, and it will likely be included as
92 part of the next major Archipelago release. I apologize for the inconvenience.
93
94Download:
95[lingo-archipelago-v5.0.1.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.0.1.zip)<br/>
96Source: [v5.0.1](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.0.1)
97
98## v5.0.0 - 2024-11-26
99
100- [Archipelago 0.5.1](https://github.com/ArchipelagoMW/Archipelago/releases/tag/0.5.1)
101 has been released! this release includes a couple of major new features for
102 Lingo:
103 - There is a new type of door shuffle called "panels mode". In panels mode,
104 the doors open when their vanilla panels are solved, but the panels
105 themselves are locked behind items. This provides a vanilla-like progression
106 route through the game, while maintaining a door shuffle amount of
107 progression gating.
108 - By default, checks that are behind your victory condition are now removed
109 from your world.
110 - There are also a number of minor tweaks and bug fixes, which you can see in
111 the above changelog.
112- This update should retain compatibility with Archipelago 0.5.0 worlds.
113- The messages in the bottom left corner now lets you know if an item you sent
114 out was hinted.
115- The text in the text client is now selectable.
116
117Download:
118[lingo-archipelago-v5.0.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v5.0.0.zip)<br/>
119Source: [v5.0.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v5.0.0)
120
121## v4.2.1 - 2024-09-21
122
123- The text client will no longer open if SHIFT is being held, to prevent
124 conflicts with the Steam Overlay.
125
126Download:
127[lingo-archipelago-v4.2.1.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v4.2.1.zip)<br/>
128Source: [v4.2.1](https://code.fourisland.com/lingo-archipelago/tag/?h=v4.2.1)
129
130## v4.2.0 - 2024-09-20
131
132- Added a proximity chat feature. You can use the command "/say" followed by a
133 message in the text client, and it will broadcast the message only to other
134 nearby Lingo players.
135
136Download:
137[lingo-archipelago-v4.2.0.zip](https://files.fourisland.com/releases/lingo-archipelago/lingo-archipelago-v4.2.0.zip)<br/>
138Source: [v4.2.0](https://code.fourisland.com/lingo-archipelago/tag/?h=v4.2.0)
139
3## v4.1.0 - 2024-09-02 140## v4.1.0 - 2024-09-02
4 141
5- Added an in-game text client, which can be used to talk to other players and 142- Added an in-game text client, which can be used to talk to other players and
diff --git a/README.md b/README.md index dcdc9cd..cfded75 100644 --- a/README.md +++ b/README.md
@@ -195,3 +195,13 @@ This is no longer relevant, because all possible pilgrimage paths are now in
195logic, but you can still see the old route 195logic, but you can still see the old route
196[starting at 2:47 in this video](https://youtu.be/8GfuDRRswdA?t=167) for 196[starting at 2:47 in this video](https://youtu.be/8GfuDRRswdA?t=167) for
197reference. 197reference.
198
199### The mod won't load! It just shows the settings screen instead.
200
201This is typically caused by playing on a severely outdated or pirated version of
202Lingo. If your copy of Lingo doesn't contain Level 2, it will not be able to run
203the Archipelago mod. It is recommended that you keep Lingo up-to-date, as the
204mod is only intended to support the latest version of the game. For instance,
205even if you are able to get the mod to run on an older version of the game, you
206may be expected to do things that aren't possible, like use passages that don't
207exist yet.
diff --git a/util/generate_gamedata.rb b/util/generate_gamedata.rb index ce8df43..7db06ca 100644 --- a/util/generate_gamedata.rb +++ b/util/generate_gamedata.rb
@@ -12,24 +12,43 @@ CLASSIFICATION_SMALL_SPHERE_ONE = 8
12 12
13panel_to_id = {} 13panel_to_id = {}
14door_groups = {} 14door_groups = {}
15panel_groups = {}
15warp_groups = {} 16warp_groups = {}
16 17
17panel_output = [] 18panel_output = []
18door_ids_by_item_id = {} 19door_ids_by_item_id = {}
19painting_ids_by_item_id = {} 20painting_ids_by_item_id = {}
21panel_ids_by_item_id = {}
20warp_ids_by_item_id = {} 22warp_ids_by_item_id = {}
21panel_ids_by_location_id = {} 23panel_ids_by_location_id = {}
22classification_by_location_id = {} 24classification_by_location_id = {}
23sunwarps = Array.new(12) {Hash.new} 25sunwarps = Array.new(12) {Hash.new}
24mentioned_doors = Set[] 26mentioned_doors = Set[]
25mentioned_paintings = Set[] 27mentioned_paintings = Set[]
28mentioned_panels = Set[]
26mentioned_warps = Set[] 29mentioned_warps = Set[]
27painting_output = {} 30painting_output = {}
28items_by_progressive_id = {} 31door_items_by_progressive_id = {}
32panel_items_by_progressive_id = {}
33panel_location_ids = []
34solve_index_by_location = {}
29 35
30ids_config = YAML.load_file(idspath) 36ids_config = YAML.load_file(idspath)
31
32config = YAML.load_file(configpath) 37config = YAML.load_file(configpath)
38
39config.each do |room_name, room_data|
40 if room_data.include? "panels"
41 room_data["panels"].each do |panel_name, panel|
42 location_id = ids_config["panels"][room_name][panel_name]
43 panel_location_ids << location_id
44 end
45 end
46end
47
48panel_location_ids.sort.each_with_index do |location_id, index|
49 solve_index_by_location[location_id] = index
50end
51
33config.each do |room_name, room_data| 52config.each do |room_name, room_data|
34 if room_data.include? "panels" 53 if room_data.include? "panels"
35 room_data["panels"].each do |panel_name, panel| 54 room_data["panels"].each do |panel_name, panel|
@@ -41,6 +60,7 @@ config.each do |room_name, room_data|
41 ret = {} 60 ret = {}
42 ret["id"] = "\"#{panel["id"]}\"" 61 ret["id"] = "\"#{panel["id"]}\""
43 ret["loc"] = location_id 62 ret["loc"] = location_id
63 ret["solve_index"] = solve_index_by_location[location_id]
44 if panel.include? "colors" 64 if panel.include? "colors"
45 if panel["colors"].kind_of? String 65 if panel["colors"].kind_of? String
46 ret["color"] = "[\"#{panel["colors"]}\"]" 66 ret["color"] = "[\"#{panel["colors"]}\"]"
@@ -69,9 +89,6 @@ config.each do |room_name, room_data|
69 if panel.include? "achievement" 89 if panel.include? "achievement"
70 ret["achievement"] = "\"#{panel["achievement"]}\"" 90 ret["achievement"] = "\"#{panel["achievement"]}\""
71 end 91 end
72 if panel.include? "hunt" and panel["hunt"]
73 ret["hunt"] = "true"
74 end
75 panel_output << ret 92 panel_output << ret
76 93
77 panel_ids_by_location_id[location_id] = [panel["id"]] 94 panel_ids_by_location_id[location_id] = [panel["id"]]
@@ -110,15 +127,29 @@ config.each do |room_name, room_data|
110 end 127 end
111 128
112 if room_data.include? "progression" 129 if room_data.include? "progression"
113 room_data["progression"].each do |progressive_item_name, progression| 130 room_data["progression"].each do |progressive_item_name, pdata|
114 progressive_id = ids_config["progression"][progressive_item_name] 131 progressive_id = ids_config["progression"][progressive_item_name]
115 items_by_progressive_id[progressive_id] = []
116 132
117 progression.each do |item| 133 if pdata.include? "doors"
118 item_room_name = (item.kind_of? Hash) ? item["room"] : room_name 134 door_items_by_progressive_id[progressive_id] = []
119 item_item_name = (item.kind_of? Hash) ? item["door"] : item 135
136 pdata["doors"].each do |item|
137 item_room_name = (item.kind_of? Hash) ? item["room"] : room_name
138 item_item_name = (item.kind_of? Hash) ? item["door"] : item
139
140 door_items_by_progressive_id[progressive_id] << ids_config["doors"][item_room_name][item_item_name]["item"]
141 end
142 end
143
144 if pdata.include? "panel_doors"
145 panel_items_by_progressive_id[progressive_id] = []
120 146
121 items_by_progressive_id[progressive_id] << ids_config["doors"][item_room_name][item_item_name]["item"] 147 pdata["panel_doors"].each do |item|
148 item_room_name = (item.kind_of? Hash) ? item["room"] : room_name
149 item_item_name = (item.kind_of? Hash) ? item["panel_door"] : item
150
151 panel_items_by_progressive_id[progressive_id] << ids_config["panel_doors"][item_room_name][item_item_name]
152 end
122 end 153 end
123 end 154 end
124 end 155 end
@@ -206,6 +237,26 @@ config.each do |room_name, room_data|
206 end 237 end
207 end 238 end
208 end 239 end
240
241 if room_data.include? "panel_doors"
242 room_data["panel_doors"].each do |panel_door_name, panel_door|
243 item_id = ids_config["panel_doors"][room_name][panel_door_name]
244
245 panel_ids_by_item_id[item_id] = panel_door["panels"].map do |panel_identifier|
246 other_room_name = (panel_identifier.kind_of? String) ? room_name : panel_identifier["room"]
247 other_panel_name = (panel_identifier.kind_of? String) ? panel_identifier : panel_identifier["panel"]
248
249 config[other_room_name]["panels"][other_panel_name]["id"]
250 end
251
252 mentioned_panels.merge(panel_ids_by_item_id[item_id])
253
254 if panel_door.include? "panel_group"
255 panel_groups[panel_door["panel_group"]] ||= Set[]
256 panel_groups[panel_door["panel_group"]].merge(panel_ids_by_item_id[item_id])
257 end
258 end
259 end
209end 260end
210 261
211door_groups.each do |group_name, door_ids| 262door_groups.each do |group_name, door_ids|
@@ -213,6 +264,11 @@ door_groups.each do |group_name, door_ids|
213 door_ids_by_item_id[item_id] = door_ids.to_a 264 door_ids_by_item_id[item_id] = door_ids.to_a
214end 265end
215 266
267panel_groups.each do |group_name, panel_ids|
268 item_id = ids_config["panel_groups"][group_name]
269 panel_ids_by_item_id[item_id] = panel_ids.to_a
270end
271
216warp_groups.each do |group_name, warp_ids| 272warp_groups.each do |group_name, warp_ids|
217 item_id = ids_config["door_groups"][group_name] 273 item_id = ids_config["door_groups"][group_name]
218 warp_ids_by_item_id[item_id] = warp_ids.to_a 274 warp_ids_by_item_id[item_id] = warp_ids.to_a
@@ -231,6 +287,12 @@ File.open(outputpath, "w") do |f|
231 "\"#{door_id}\"" 287 "\"#{door_id}\""
232 end.join(",") + "]" 288 end.join(",") + "]"
233 end.join(",")) 289 end.join(","))
290 f.write "}\nvar panel_ids_by_item_id = {"
291 f.write(panel_ids_by_item_id.map do |item_id, panel_ids|
292 "#{item_id}:[" + panel_ids.map do |panel_id|
293 "\"#{panel_id}\""
294 end.join(",") + "]"
295 end.join(","))
234 f.write "}\nvar painting_ids_by_item_id = {" 296 f.write "}\nvar painting_ids_by_item_id = {"
235 f.write(painting_ids_by_item_id.map do |item_id, painting_ids| 297 f.write(painting_ids_by_item_id.map do |item_id, painting_ids|
236 "#{item_id}:[" + painting_ids.map do |painting_id| 298 "#{item_id}:[" + painting_ids.map do |painting_id|
@@ -257,6 +319,10 @@ File.open(outputpath, "w") do |f|
257 f.write(mentioned_paintings.map do |painting_id| 319 f.write(mentioned_paintings.map do |painting_id|
258 "\"#{painting_id}\"" 320 "\"#{painting_id}\""
259 end.join(",")) 321 end.join(","))
322 f.write "]\nvar mentioned_panels = ["
323 f.write(mentioned_panels.map do |panel_id|
324 "\"#{panel_id}\""
325 end.join(","))
260 f.write "]\nvar mentioned_warps = [" 326 f.write "]\nvar mentioned_warps = ["
261 f.write(mentioned_warps.map do |warp_id| 327 f.write(mentioned_warps.map do |warp_id|
262 "\"#{warp_id}\"" 328 "\"#{warp_id}\""
@@ -273,8 +339,12 @@ File.open(outputpath, "w") do |f|
273 f.write(sunwarps.map do |sunwarp| 339 f.write(sunwarps.map do |sunwarp|
274 "{\"orientation\":\"#{sunwarp["orientation"]}\",\"entrance_indicator_pos\":#{sunwarp["entrance_indicator_pos"].to_s}}" 340 "{\"orientation\":\"#{sunwarp["orientation"]}\",\"entrance_indicator_pos\":#{sunwarp["entrance_indicator_pos"].to_s}}"
275 end.join(",")) 341 end.join(","))
276 f.write "]\nvar items_by_progressive_id = {" 342 f.write "]\nvar door_items_by_progressive_id = {"
277 f.write(items_by_progressive_id.map do |item_id, progression_ids| 343 f.write(door_items_by_progressive_id.map do |item_id, progression_ids|
344 "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]"
345 end.join(","))
346 f.write "}\nvar panel_items_by_progressive_id = {"
347 f.write(panel_items_by_progressive_id.map do |item_id, progression_ids|
278 "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" 348 "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]"
279 end.join(",")) 349 end.join(","))
280 f.write "}" 350 f.write "}"