diff options
| -rw-r--r-- | Archipelago/client.gd | 81 | ||||
| -rw-r--r-- | Archipelago/load.gd | 2 | ||||
| -rw-r--r-- | Archipelago/panel.gd | 9 | ||||
| -rw-r--r-- | util/generate_gamedata.rb | 75 | 
4 files changed, 148 insertions, 19 deletions
| diff --git a/Archipelago/client.gd b/Archipelago/client.gd index 7903df2..0944352 100644 --- a/Archipelago/client.gd +++ b/Archipelago/client.gd | |||
| @@ -23,7 +23,7 @@ const ap_version = {"major": 0, "minor": 4, "build": 6, "class": "Version"} | |||
| 23 | const color_items = [ | 23 | const color_items = [ | 
| 24 | "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow" | 24 | "White", "Black", "Red", "Blue", "Green", "Brown", "Gray", "Orange", "Purple", "Yellow" | 
| 25 | ] | 25 | ] | 
| 26 | const progressive_items = { | 26 | const door_progressive_items = { | 
| 27 | "Progressive Orange Tower": | 27 | "Progressive Orange Tower": | 
| 28 | ["Second Floor", "Third Floor", "Fourth Floor", "Fifth Floor", "Sixth Floor", "Seventh Floor"], | 28 | ["Second Floor", "Third Floor", "Fourth Floor", "Fifth Floor", "Sixth Floor", "Seventh Floor"], | 
| 29 | "Progressive Art Gallery": | 29 | "Progressive Art Gallery": | 
| @@ -35,6 +35,40 @@ const progressive_items = { | |||
| 35 | "Progressive Pilgrimage": | 35 | "Progressive Pilgrimage": | 
| 36 | ["1 Sunwarp", "2 Sunwarp", "3 Sunwarp", "4 Sunwarp", "5 Sunwarp", "6 Sunwarp"] | 36 | ["1 Sunwarp", "2 Sunwarp", "3 Sunwarp", "4 Sunwarp", "5 Sunwarp", "6 Sunwarp"] | 
| 37 | } | 37 | } | 
| 38 | const panel_progressive_items = { | ||
| 39 | "Progressive Hallway Room": | ||
| 40 | [ | ||
| 41 | {"item": "Hallway Room - First Room Panels", "display": "First Door"}, | ||
| 42 | {"item": "Hallway Room - Second Room Panels", "display": "Second Door"}, | ||
| 43 | {"item": "Hallway Room - Third Room Panels", "display": "Third Door"}, | ||
| 44 | {"item": "Hallway Room - WHEEL", "display": "Fourth Door"}, | ||
| 45 | ], | ||
| 46 | "Progressive Colorful": | ||
| 47 | [ | ||
| 48 | {"item": "The Colorful - BEGIN (Panel)", "display": "White"}, | ||
| 49 | {"item": "The Colorful - FOUND (Panel)", "display": "Black"}, | ||
| 50 | {"item": "The Colorful - LOAF (Panel)", "display": "Red"}, | ||
| 51 | {"item": "The Colorful - CREAM (Panel)", "display": "Yellow"}, | ||
| 52 | {"item": "The Colorful - SUN (Panel)", "display": "Blue"}, | ||
| 53 | {"item": "The Colorful - SPOON (Panel)", "display": "Purple"}, | ||
| 54 | {"item": "The Colorful - LETTERS (Panel)", "display": "Orange"}, | ||
| 55 | {"item": "The Colorful - WALLS (Panel)", "display": "Green"}, | ||
| 56 | {"item": "The Colorful - IRON (Panel)", "display": "Brown"}, | ||
| 57 | {"item": "The Colorful - OBSTACLE (Panel)", "display": "Gray"}, | ||
| 58 | ], | ||
| 59 | "Progressive Number Hunt": | ||
| 60 | [ | ||
| 61 | {"item": "Two Panels", "display": "Two"}, | ||
| 62 | {"item": "Three Panels", "display": "Three"}, | ||
| 63 | {"item": "Four Panels", "display": "Four"}, | ||
| 64 | {"item": "Five Panels", "display": "Five"}, | ||
| 65 | {"item": "Six Panels", "display": "Six"}, | ||
| 66 | {"item": "Seven Panels", "display": "Seven"}, | ||
| 67 | {"item": "Eight Panels", "display": "Eight"}, | ||
| 68 | {"item": "Nine Panels", "display": "Nine"}, | ||
| 69 | {"item": "Outside The Undeterred - ZERO (Panel)", "display": "Zero"}, | ||
| 70 | ] | ||
| 71 | } | ||
| 38 | 72 | ||
| 39 | const kTHE_END = 0 | 73 | const kTHE_END = 0 | 
| 40 | const kTHE_MASTER = 1 | 74 | const kTHE_MASTER = 1 | 
| @@ -87,6 +121,7 @@ var _localdata_file = "" | |||
| 87 | var _death_link = false | 121 | var _death_link = false | 
| 88 | var _victory_condition = 0 # THE END, THE MASTER, LEVEL 2 | 122 | var _victory_condition = 0 # THE END, THE MASTER, LEVEL 2 | 
| 89 | var _door_shuffle = false | 123 | var _door_shuffle = false | 
| 124 | var _panel_door_shuffle = false | ||
| 90 | var _color_shuffle = false | 125 | var _color_shuffle = false | 
| 91 | var _panel_shuffle = 0 # none, rearrange | 126 | var _panel_shuffle = 0 # none, rearrange | 
| 92 | var _painting_shuffle = false | 127 | var _painting_shuffle = false | 
| @@ -266,7 +301,12 @@ func _on_data(): | |||
| 266 | if _slot_data.has("shuffle_colors"): | 301 | if _slot_data.has("shuffle_colors"): | 
| 267 | _color_shuffle = _slot_data["shuffle_colors"] | 302 | _color_shuffle = _slot_data["shuffle_colors"] | 
| 268 | if _slot_data.has("shuffle_doors"): | 303 | if _slot_data.has("shuffle_doors"): | 
| 269 | _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 | ||
| 270 | if _slot_data.has("shuffle_paintings"): | 310 | if _slot_data.has("shuffle_paintings"): | 
| 271 | _painting_shuffle = _slot_data["shuffle_paintings"] | 311 | _painting_shuffle = _slot_data["shuffle_paintings"] | 
| 272 | if _slot_data.has("shuffle_panels"): | 312 | if _slot_data.has("shuffle_panels"): | 
| @@ -680,6 +720,12 @@ func processItem(item, index, from, flags): | |||
| 680 | for door_id in gamedata.door_ids_by_item_id[int(item)]: | 720 | for door_id in gamedata.door_ids_by_item_id[int(item)]: | 
| 681 | doorsNode.get_node(door_id).openDoor() | 721 | doorsNode.get_node(door_id).openDoor() | 
| 682 | 722 | ||
| 723 | if gamedata.panel_ids_by_item_id.has(int(item)): | ||
| 724 | var panelsNode = get_tree().get_root().get_node("Spatial/Panels") | ||
| 725 | for panel_id in gamedata.panel_ids_by_item_id[int(item)]: | ||
| 726 | panelsNode.get_node(panel_id).get_node("AP_Panel").locked = false | ||
| 727 | emit_signal("evaluate_solvability") | ||
| 728 | |||
| 683 | if gamedata.painting_ids_by_item_id.has(int(item)): | 729 | if gamedata.painting_ids_by_item_id.has(int(item)): | 
| 684 | var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings") | 730 | var real_parent_node = get_tree().get_root().get_node("Spatial/Decorations/Paintings") | 
| 685 | var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings") | 731 | var fake_parent_node = get_tree().get_root().get_node_or_null("Spatial/AP_Paintings") | 
| @@ -700,13 +746,26 @@ func processItem(item, index, from, flags): | |||
| 700 | warpsNode.get_node(warp_id).unlock_warp() | 746 | warpsNode.get_node(warp_id).unlock_warp() | 
| 701 | 747 | ||
| 702 | # Handle progressive items. | 748 | # Handle progressive items. | 
| 703 | if int(item) in gamedata.items_by_progressive_id.keys(): | 749 | var is_progressive_door = int(item) in gamedata.door_items_by_progressive_id | 
| 750 | var is_progressive_panel = int(item) in gamedata.panel_items_by_progressive_id | ||
| 751 | var progitems = null | ||
| 752 | |||
| 753 | if is_progressive_door and is_progressive_panel: | ||
| 754 | if _door_shuffle: | ||
| 755 | progitems = gamedata.door_items_by_progressive_id[int(item)] | ||
| 756 | else: | ||
| 757 | progitems = gamedata.panel_items_by_progressive_id[int(item)] | ||
| 758 | elif is_progressive_door: | ||
| 759 | progitems = gamedata.door_items_by_progressive_id[int(item)] | ||
| 760 | elif is_progressive_panel: | ||
| 761 | progitems = gamedata.panel_items_by_progressive_id[int(item)] | ||
| 762 | |||
| 763 | if progitems != null: | ||
| 704 | if not int(item) in _progressive_progress: | 764 | if not int(item) in _progressive_progress: | 
| 705 | _progressive_progress[int(item)] = 0 | 765 | _progressive_progress[int(item)] = 0 | 
| 706 | 766 | ||
| 707 | if _progressive_progress[int(item)] < gamedata.items_by_progressive_id[int(item)].size(): | 767 | if _progressive_progress[int(item)] < progitems.size(): | 
| 708 | var subitems = gamedata.items_by_progressive_id[int(item)] | 768 | var subitem_id = progitems[_progressive_progress[int(item)]] | 
| 709 | var subitem_id = subitems[_progressive_progress[int(item)]] | ||
| 710 | global._print("Subitem: %d" % subitem_id) | 769 | global._print("Subitem: %d" % subitem_id) | 
| 711 | processItem(subitem_id, null, null, null) | 770 | processItem(subitem_id, null, null, null) | 
| 712 | _progressive_progress[int(item)] += 1 | 771 | _progressive_progress[int(item)] += 1 | 
| @@ -722,9 +781,13 @@ func processItem(item, index, from, flags): | |||
| 722 | _last_new_item = index | 781 | _last_new_item = index | 
| 723 | saveLocaldata() | 782 | saveLocaldata() | 
| 724 | 783 | ||
| 725 | if item_name in progressive_items: | 784 | if _door_shuffle and item_name in door_progressive_items: | 
| 726 | var subitem = progressive_items[item_name][_progressive_progress[int(item)] - 1] | 785 | var subitem = door_progressive_items[item_name][_progressive_progress[int(item)] - 1] | 
| 727 | item_name += " (%s)" % subitem | 786 | item_name += " (%s)" % subitem["display"] | 
| 787 | |||
| 788 | if _panel_door_shuffle and item_name in panel_progressive_items: | ||
| 789 | var subitem = panel_progressive_items[item_name][_progressive_progress[int(item)] - 1] | ||
| 790 | item_name += " (%s)" % subitem["display"] | ||
| 728 | 791 | ||
| 729 | var player_name = "Unknown" | 792 | var player_name = "Unknown" | 
| 730 | if _player_name_by_slot.has(from): | 793 | if _player_name_by_slot.has(from): | 
| diff --git a/Archipelago/load.gd b/Archipelago/load.gd index 0ed978a..6ce4749 100644 --- a/Archipelago/load.gd +++ b/Archipelago/load.gd | |||
| @@ -571,6 +571,8 @@ func _load(): | |||
| 571 | var script_instance = panel_script.new() | 571 | var script_instance = panel_script.new() | 
| 572 | script_instance.name = "AP_Panel" | 572 | script_instance.name = "AP_Panel" | 
| 573 | script_instance.data = panel | 573 | script_instance.data = panel | 
| 574 | if apclient._panel_door_shuffle and gamedata.mentioned_panels.has(panel["id"]): | ||
| 575 | script_instance.locked = true | ||
| 574 | panel_node.add_child(script_instance) | 576 | panel_node.add_child(script_instance) | 
| 575 | apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability") | 577 | apclient.connect("evaluate_solvability", script_instance, "evaluate_solvability") | 
| 576 | 578 | ||
| diff --git a/Archipelago/panel.gd b/Archipelago/panel.gd index fc5963a..9d30455 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-+" | 
| @@ -45,7 +46,13 @@ func evaluate_solvability(): | |||
| 45 | missing.append(color) | 46 | missing.append(color) | 
| 46 | solvable = false | 47 | solvable = false | 
| 47 | 48 | ||
| 48 | if solvable: | 49 | if locked: | 
| 50 | self.get_parent().get_node("Viewport/GUI/Panel/Label").text = "Locked" | ||
| 51 | self.get_parent().get_node("Viewport/GUI/Panel/TextEdit").editable = false | ||
| 52 | self.get_parent().get_node("Quad").get_surface_material(0).albedo_color = Color( | ||
| 53 | 0.2, 0.7, 0.7 | ||
| 54 | ) | ||
| 55 | elif solvable: | ||
| 49 | if effects.atbash_remaining > 0: | 56 | if effects.atbash_remaining > 0: | 
| 50 | self.get_parent().get_node("Viewport/GUI/Panel/Label").text = atbash_text | 57 | self.get_parent().get_node("Viewport/GUI/Panel/Label").text = atbash_text | 
| 51 | else: | 58 | else: | 
| diff --git a/util/generate_gamedata.rb b/util/generate_gamedata.rb index 1f18b7e..4f9dfd7 100644 --- a/util/generate_gamedata.rb +++ b/util/generate_gamedata.rb | |||
| @@ -11,20 +11,24 @@ CLASSIFICATION_INSANITY = 4 | |||
| 11 | 11 | ||
| 12 | panel_to_id = {} | 12 | panel_to_id = {} | 
| 13 | door_groups = {} | 13 | door_groups = {} | 
| 14 | panel_groups = {} | ||
| 14 | warp_groups = {} | 15 | warp_groups = {} | 
| 15 | 16 | ||
| 16 | panel_output = [] | 17 | panel_output = [] | 
| 17 | door_ids_by_item_id = {} | 18 | door_ids_by_item_id = {} | 
| 18 | painting_ids_by_item_id = {} | 19 | painting_ids_by_item_id = {} | 
| 20 | panel_ids_by_item_id = {} | ||
| 19 | warp_ids_by_item_id = {} | 21 | warp_ids_by_item_id = {} | 
| 20 | panel_ids_by_location_id = {} | 22 | panel_ids_by_location_id = {} | 
| 21 | classification_by_location_id = {} | 23 | classification_by_location_id = {} | 
| 22 | sunwarps = Array.new(12) {Hash.new} | 24 | sunwarps = Array.new(12) {Hash.new} | 
| 23 | mentioned_doors = Set[] | 25 | mentioned_doors = Set[] | 
| 24 | mentioned_paintings = Set[] | 26 | mentioned_paintings = Set[] | 
| 27 | mentioned_panels = Set[] | ||
| 25 | mentioned_warps = Set[] | 28 | mentioned_warps = Set[] | 
| 26 | painting_output = {} | 29 | painting_output = {} | 
| 27 | items_by_progressive_id = {} | 30 | door_items_by_progressive_id = {} | 
| 31 | panel_items_by_progressive_id = {} | ||
| 28 | 32 | ||
| 29 | ids_config = YAML.load_file(idspath) | 33 | ids_config = YAML.load_file(idspath) | 
| 30 | 34 | ||
| @@ -105,15 +109,29 @@ config.each do |room_name, room_data| | |||
| 105 | end | 109 | end | 
| 106 | 110 | ||
| 107 | if room_data.include? "progression" | 111 | if room_data.include? "progression" | 
| 108 | room_data["progression"].each do |progressive_item_name, progression| | 112 | room_data["progression"].each do |progressive_item_name, pdata| | 
| 109 | progressive_id = ids_config["progression"][progressive_item_name] | 113 | progressive_id = ids_config["progression"][progressive_item_name] | 
| 110 | items_by_progressive_id[progressive_id] = [] | ||
| 111 | 114 | ||
| 112 | progression.each do |item| | 115 | if pdata.include? "doors" | 
| 113 | item_room_name = (item.kind_of? Hash) ? item["room"] : room_name | 116 | door_items_by_progressive_id[progressive_id] = [] | 
| 114 | item_item_name = (item.kind_of? Hash) ? item["door"] : item | ||
| 115 | 117 | ||
| 116 | items_by_progressive_id[progressive_id] << ids_config["doors"][item_room_name][item_item_name]["item"] | 118 | pdata["doors"].each do |item| | 
| 119 | item_room_name = (item.kind_of? Hash) ? item["room"] : room_name | ||
| 120 | item_item_name = (item.kind_of? Hash) ? item["door"] : item | ||
| 121 | |||
| 122 | door_items_by_progressive_id[progressive_id] << ids_config["doors"][item_room_name][item_item_name]["item"] | ||
| 123 | end | ||
| 124 | end | ||
| 125 | |||
| 126 | if pdata.include? "panel_doors" | ||
| 127 | panel_items_by_progressive_id[progressive_id] = [] | ||
| 128 | |||
| 129 | pdata["panel_doors"].each do |item| | ||
| 130 | item_room_name = (item.kind_of? Hash) ? item["room"] : room_name | ||
| 131 | item_item_name = (item.kind_of? Hash) ? item["panel_door"] : item | ||
| 132 | |||
| 133 | panel_items_by_progressive_id[progressive_id] << ids_config["panel_doors"][item_room_name][item_item_name] | ||
| 134 | end | ||
| 117 | end | 135 | end | 
| 118 | end | 136 | end | 
| 119 | end | 137 | end | 
| @@ -201,6 +219,26 @@ config.each do |room_name, room_data| | |||
| 201 | end | 219 | end | 
| 202 | end | 220 | end | 
| 203 | end | 221 | end | 
| 222 | |||
| 223 | if room_data.include? "panel_doors" | ||
| 224 | room_data["panel_doors"].each do |panel_door_name, panel_door| | ||
| 225 | item_id = ids_config["panel_doors"][room_name][panel_door_name] | ||
| 226 | |||
| 227 | panel_ids_by_item_id[item_id] = panel_door["panels"].map do |panel_identifier| | ||
| 228 | other_room_name = (panel_identifier.kind_of? String) ? room_name : panel_identifier["room"] | ||
| 229 | other_panel_name = (panel_identifier.kind_of? String) ? panel_identifier : panel_identifier["panel"] | ||
| 230 | |||
| 231 | config[other_room_name]["panels"][other_panel_name]["id"] | ||
| 232 | end | ||
| 233 | |||
| 234 | mentioned_panels.merge(panel_ids_by_item_id[item_id]) | ||
| 235 | |||
| 236 | if panel_door.include? "panel_group" | ||
| 237 | panel_groups[panel_door["panel_group"]] ||= Set[] | ||
| 238 | panel_groups[panel_door["panel_group"]].merge(panel_ids_by_item_id[item_id]) | ||
| 239 | end | ||
| 240 | end | ||
| 241 | end | ||
| 204 | end | 242 | end | 
| 205 | 243 | ||
| 206 | door_groups.each do |group_name, door_ids| | 244 | door_groups.each do |group_name, door_ids| | 
| @@ -208,6 +246,11 @@ door_groups.each do |group_name, door_ids| | |||
| 208 | door_ids_by_item_id[item_id] = door_ids.to_a | 246 | door_ids_by_item_id[item_id] = door_ids.to_a | 
| 209 | end | 247 | end | 
| 210 | 248 | ||
| 249 | panel_groups.each do |group_name, panel_ids| | ||
| 250 | item_id = ids_config["panel_groups"][group_name] | ||
| 251 | panel_ids_by_item_id[item_id] = panel_ids.to_a | ||
| 252 | end | ||
| 253 | |||
| 211 | warp_groups.each do |group_name, warp_ids| | 254 | warp_groups.each do |group_name, warp_ids| | 
| 212 | item_id = ids_config["door_groups"][group_name] | 255 | item_id = ids_config["door_groups"][group_name] | 
| 213 | warp_ids_by_item_id[item_id] = warp_ids.to_a | 256 | warp_ids_by_item_id[item_id] = warp_ids.to_a | 
| @@ -226,6 +269,12 @@ File.open(outputpath, "w") do |f| | |||
| 226 | "\"#{door_id}\"" | 269 | "\"#{door_id}\"" | 
| 227 | end.join(",") + "]" | 270 | end.join(",") + "]" | 
| 228 | end.join(",")) | 271 | end.join(",")) | 
| 272 | f.write "}\nvar panel_ids_by_item_id = {" | ||
| 273 | f.write(panel_ids_by_item_id.map do |item_id, panel_ids| | ||
| 274 | "#{item_id}:[" + panel_ids.map do |panel_id| | ||
| 275 | "\"#{panel_id}\"" | ||
| 276 | end.join(",") + "]" | ||
| 277 | end.join(",")) | ||
| 229 | f.write "}\nvar painting_ids_by_item_id = {" | 278 | f.write "}\nvar painting_ids_by_item_id = {" | 
| 230 | f.write(painting_ids_by_item_id.map do |item_id, painting_ids| | 279 | f.write(painting_ids_by_item_id.map do |item_id, painting_ids| | 
| 231 | "#{item_id}:[" + painting_ids.map do |painting_id| | 280 | "#{item_id}:[" + painting_ids.map do |painting_id| | 
| @@ -252,6 +301,10 @@ File.open(outputpath, "w") do |f| | |||
| 252 | f.write(mentioned_paintings.map do |painting_id| | 301 | f.write(mentioned_paintings.map do |painting_id| | 
| 253 | "\"#{painting_id}\"" | 302 | "\"#{painting_id}\"" | 
| 254 | end.join(",")) | 303 | end.join(",")) | 
| 304 | f.write "]\nvar mentioned_panels = [" | ||
| 305 | f.write(mentioned_panels.map do |panel_id| | ||
| 306 | "\"#{panel_id}\"" | ||
| 307 | end.join(",")) | ||
| 255 | f.write "]\nvar mentioned_warps = [" | 308 | f.write "]\nvar mentioned_warps = [" | 
| 256 | f.write(mentioned_warps.map do |warp_id| | 309 | f.write(mentioned_warps.map do |warp_id| | 
| 257 | "\"#{warp_id}\"" | 310 | "\"#{warp_id}\"" | 
| @@ -268,8 +321,12 @@ File.open(outputpath, "w") do |f| | |||
| 268 | f.write(sunwarps.map do |sunwarp| | 321 | f.write(sunwarps.map do |sunwarp| | 
| 269 | "{\"orientation\":\"#{sunwarp["orientation"]}\",\"entrance_indicator_pos\":#{sunwarp["entrance_indicator_pos"].to_s}}" | 322 | "{\"orientation\":\"#{sunwarp["orientation"]}\",\"entrance_indicator_pos\":#{sunwarp["entrance_indicator_pos"].to_s}}" | 
| 270 | end.join(",")) | 323 | end.join(",")) | 
| 271 | f.write "]\nvar items_by_progressive_id = {" | 324 | f.write "]\nvar door_items_by_progressive_id = {" | 
| 272 | f.write(items_by_progressive_id.map do |item_id, progression_ids| | 325 | f.write(door_items_by_progressive_id.map do |item_id, progression_ids| | 
| 326 | "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" | ||
| 327 | end.join(",")) | ||
| 328 | f.write "}\nvar panel_items_by_progressive_id = {" | ||
| 329 | f.write(panel_items_by_progressive_id.map do |item_id, progression_ids| | ||
| 273 | "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" | 330 | "#{item_id}:[" + progression_ids.map(&:to_s).join(",") + "]" | 
| 274 | end.join(",")) | 331 | end.join(",")) | 
| 275 | f.write "}" | 332 | f.write "}" | 
