LevelSaver.gd 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. class_name LevelSaver
  2. extends Node
  3. var sub_level_file := {"Layers": [{}, {}, {}, {}, {}], "Data": "", "BG": ""}
  4. static var level_file := {"Info": {}, "Levels": [{}, {}, {}, {}, {}]}
  5. @onready var editor: LevelEditor = owner
  6. const chunk_template := {"Tiles": "", "Entities": ""}
  7. const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  8. @onready var level: Level = $"../Level"
  9. @onready var level_bg: LevelBG = $"../Level/LevelBG"
  10. var entity_map := {}
  11. const tile_blacklist := []
  12. func _ready() -> void:
  13. load_entity_map()
  14. func save_level(level_name := "Unnamed Level", level_author := "You", level_desc := "No Desc", difficulty := 0) -> Dictionary:
  15. level_file = editor.level_file
  16. sub_level_file = {"Layers": [{}, {}, {}, {}, {}], "Data": "", "BG": ""}
  17. get_tiles()
  18. get_entities()
  19. save_bg_data()
  20. save_level_data()
  21. level_file["Levels"][editor.sub_level_id] = sub_level_file.duplicate()
  22. level_file["Info"] = {"Name": level_name, "Author": level_author, "Description": level_desc, "Difficulty": difficulty}
  23. level_file["Version"] = Global.version_number
  24. return level_file
  25. func write_file(json := {}, lvl_file_name := "") -> void:
  26. DirAccess.make_dir_absolute(LevelEditor.CUSTOM_LEVEL_DIR)
  27. for i in "<>:?!/":
  28. lvl_file_name = lvl_file_name.replace(i, "")
  29. var file = FileAccess.open(LevelEditor.CUSTOM_LEVEL_DIR + lvl_file_name, FileAccess.WRITE)
  30. file.store_string(JSON.stringify(json, "", false))
  31. file.close()
  32. print("saved")
  33. func load_entity_map() -> void:
  34. entity_map = JSON.parse_string(FileAccess.open(EntityIDMapper.MAP_PATH, FileAccess.READ).get_as_text())
  35. func get_tiles() -> void:
  36. for layer in 5:
  37. for tile in editor.tile_layer_nodes[layer].get_used_cells():
  38. if tile_blacklist.has(editor.tile_layer_nodes[layer].get_cell_atlas_coords(tile)) or editor.tile_layer_nodes[layer].get_cell_source_id(tile) == 6:
  39. continue
  40. var tile_string := ""
  41. var chunk_tile = Vector2i(wrap(tile.x, 0, 32), wrap(tile.y + 30, -1, 32))
  42. tile_string += base64_charset[chunk_tile.x]
  43. tile_string += base64_charset[chunk_tile.y]
  44. tile_string += ""
  45. tile_string += base64_charset[editor.tile_layer_nodes[layer].get_cell_atlas_coords(tile).x]
  46. tile_string += base64_charset[editor.tile_layer_nodes[layer].get_cell_atlas_coords(tile).y]
  47. tile_string += base64_charset[editor.tile_layer_nodes[layer].get_cell_source_id(tile)]
  48. var tile_chunk_idx = tile_to_chunk_idx(tile)
  49. var tile_chunk := {}
  50. if sub_level_file["Layers"][layer].has(tile_chunk_idx):
  51. tile_chunk = sub_level_file["Layers"][layer][tile_chunk_idx]
  52. else:
  53. tile_chunk = chunk_template.duplicate(true)
  54. tile_chunk["Tiles"] += tile_string + "="
  55. sub_level_file["Layers"][layer][tile_chunk_idx] = tile_chunk
  56. for i in sub_level_file["Layers"][layer]:
  57. sub_level_file["Layers"][layer][i]["Tiles"] = compress_string(sub_level_file["Layers"][layer][i]["Tiles"])
  58. static func compress_string(buffer := "") -> String:
  59. var bytes = buffer.to_ascii_buffer()
  60. var compressed_bytes = bytes.compress(FileAccess.CompressionMode.COMPRESSION_DEFLATE)
  61. var b64_buffer = Marshalls.raw_to_base64(compressed_bytes)
  62. # workaround since for some reason .replace() decided not to work today
  63. b64_buffer = b64_buffer.replace("=", "%")
  64. return b64_buffer
  65. static func decompress_string(buffer := "") -> String:
  66. if buffer.is_empty():
  67. return buffer
  68. buffer = buffer.replace("%", "=")
  69. var compressed = Marshalls.base64_to_raw(buffer)
  70. var decompressed = compressed.decompress_dynamic(-1, FileAccess.CompressionMode.COMPRESSION_DEFLATE)
  71. var ret = decompressed.get_string_from_ascii()
  72. return ret
  73. func get_entities() -> void:
  74. for layer in 5:
  75. for entity in editor.entity_layer_nodes[layer].get_children():
  76. if entity.has_meta("tile_position") == false:
  77. continue
  78. var entity_string := ""
  79. var chunk_position = Vector2i(wrap(entity.get_meta("tile_position").x, 0, 32), wrap(entity.get_meta("tile_position").y + 30, 0, 32))
  80. entity_string += base64_charset[chunk_position.x]
  81. entity_string += base64_charset[chunk_position.y]
  82. entity_string += ","
  83. entity_string += EntityIDMapper.get_map_id(entity.scene_file_path)
  84. if entity.has_node("EditorPropertyExposer"):
  85. entity_string += entity.get_node("EditorPropertyExposer").get_string()
  86. var entity_chunk_idx = tile_to_chunk_idx(entity.get_meta("tile_position"))
  87. var tile_chunk := {}
  88. if sub_level_file["Layers"][layer].has(entity_chunk_idx):
  89. tile_chunk = sub_level_file["Layers"][layer][entity_chunk_idx]
  90. else:
  91. tile_chunk = chunk_template.duplicate(true)
  92. tile_chunk["Entities"] += entity_string + "="
  93. sub_level_file["Layers"][layer][entity_chunk_idx] = tile_chunk
  94. for i in sub_level_file["Layers"][layer]:
  95. sub_level_file["Layers"][layer][i]["Entities"] = compress_string(sub_level_file["Layers"][layer][i]["Entities"])
  96. func encode_to_base64_2char(value: int) -> String:
  97. if value < 0 or value >= 4096:
  98. push_error("Value out of range for 2-char base64 encoding.")
  99. return ""
  100. var char1 = base64_charset[(value >> 6) & 0b111111] # Top 6 bits
  101. var char2 = base64_charset[value & 0b111111] # Bottom 6 bits
  102. return char1 + char2
  103. func save_level_data() -> void:
  104. var string := ""
  105. for i in [Level.THEME_IDXS.find(level.theme), ["Day", "Night"].find(level.theme_time), editor.bgm_id, ["SMB1", "SMBLL", "SMBS", "SMBANN"].find(level.campaign), level.can_backscroll, abs(level.vertical_height), level.time_limit]:
  106. var key := ""
  107. if int(i) >= 64:
  108. key = encode_to_base64_2char(int(i))
  109. else:
  110. key = base64_charset[int(i)]
  111. string += key + "="
  112. sub_level_file["Data"] = string
  113. func save_bg_data() -> void:
  114. var string := ""
  115. for i in [level_bg.primary_layer, level_bg.second_layer, level_bg.second_layer_offset.y, level_bg.time_of_day, level_bg.particles, level_bg.liquid_layer, level_bg.overlay_clouds]:
  116. var key := ""
  117. i = int(i)
  118. if abs(i) >= 64:
  119. key = encode_to_base64_2char(abs(i))
  120. else:
  121. key = base64_charset[abs(i)]
  122. string += key + "="
  123. sub_level_file["BG"] = string
  124. func tile_to_chunk_idx(tile_position := Vector2i.ZERO) -> int:
  125. return floor(tile_position.x / 32.0)