diff options
| -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 "}" |
