312 lines
8.9 KiB
GDScript

@tool
extends EditorImportPlugin
const LDTK_LATEST_VERSION = "1.5.3"
enum Presets {DEFAULT}
const Util = preload("src/util/util.gd")
const World = preload("src/world.gd")
const Level = preload("src/level.gd")
const Tileset = preload("src/tileset.gd")
const DefinitionUtil = preload("src/util/definition_util.gd")
#region EditorImportPlugin Overrides
#region Simple
func _get_importer_name():
return "ldtk.import"
func _get_visible_name():
return "LDTK Scene"
func _get_priority():
return 1.0
func _get_import_order():
return IMPORT_ORDER_SCENE
func _get_resource_type():
return "PackedScene"
func _get_recognized_extensions():
return ["ldtk"]
func _get_save_extension():
return "scn"
func _get_preset_count():
return Presets.size()
func _get_preset_name(index):
match index:
Presets.DEFAULT:
return "Default"
_:
return "Unknown"
func _get_option_visibility(path, option_name, options):
match option_name:
_:
return true
return true
func _can_import_threaded() -> bool:
return false
#endregion
func _get_import_options(path, index):
return [
# --- World --- #
{"name": "World", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
# Group LDTKLevels in 'LDTKWorldLayer' nodes if using LDTK's WorldDepth.
"name": "group_world_layers",
"default_value": false,
},
# --- Levels --- #
{"name": "Level", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
# Save LDTKLevels as PackedScenes.
"name": "pack_levels",
"default_value": true,
},
# --- Layers --- #
{"name": "Layer", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
# Save LDTKLevels as PackedScenes.
"name": "layers_always_visible",
"default_value": false,
},
# --- Tileset --- #
{"name": "Tileset", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
# Add LDTK Custom Data to Tilesets
"name": "tileset_custom_data",
"default_value": false,
},
{
# Create TileAtlasSources & TileMapLayers for IntGrid Layers
"name": "integer_grid_tilesets",
"default_value": false,
},
{
# Define default texture type for TilesetAtlasSource (e.g. to apply normal maps to tilesets after import)
"name": "atlas_texture_type",
"default_value": 0,
"property_hint": PROPERTY_HINT_ENUM,
"hint_string": "CompressedTexture2D,CanvasTexture",
},
# --- Entities --- #
{"name": "Entity", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
#
"name": "resolve_entityrefs",
"default_value": true,
},
{
# Create LDTKEntityPlaceholder nodes to help debug importing.
"name": "use_entity_placeholders",
"default_value": false,
},
# --- Post Import --- #
{"name": "Post Import", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
# Define a post-import script to apply on imported Tilesets.
"name": "tileset_post_import",
"default_value": "",
"property_hint": PROPERTY_HINT_FILE,
"hint_string": "*.gd;GDScript"
},
{
# Define a post-import script to apply on imported Entities.
"name": "entities_post_import",
"default_value": "",
"property_hint": PROPERTY_HINT_FILE,
"hint_string": "*.gd;GDScript"
},
{
# Define a post-import script to apply on imported Levels.
"name": "level_post_import",
"default_value": "",
"property_hint": PROPERTY_HINT_FILE,
"hint_string": "*.gd;GDScript"
},
{
# Define a post-import script to apply on imported Worlds.
"name": "world_post_import",
"default_value": "",
"property_hint": PROPERTY_HINT_FILE,
"hint_string": "*.gd;GDScript"
},
# --- Debug --- #
{"name": "Debug", "default_value":"", "usage": PROPERTY_USAGE_GROUP},
{
# Force Tilesets to be recreated, resetting modifications (if experiencing import issues)
"name": "force_tileset_reimport",
"default_value": false,
},
{
# Debug: Enable Verbose Output (used by the importer)
"name": "verbose_output", "default_value": false
}
]
func _import(
source_file: String,
save_path: String,
options: Dictionary,
platform_variants: Array[String],
gen_files: Array[String]
) -> Error:
Util.timer_reset()
Util.timer_start(Util.DebugTime.TOTAL)
Util.print("import_start", source_file)
# Add options to static var in "Util", accessible from any script.
Util.options = options
# Parse source_file
var base_dir := source_file.get_base_dir() + "/"
var file_name := source_file.get_file()
var world_name := file_name.split(".")[0]
Util.timer_start(Util.DebugTime.LOAD)
var world_data := Util.parse_file(source_file)
Util.timer_finish("File parsed")
# Check version
if Util.check_version(world_data.jsonVersion, LDTK_LATEST_VERSION):
Util.print("item_ok", "LDTK VERSION (%s) OK" % [world_data.jsonVersion])
else:
return ERR_PARSE_ERROR
Util.timer_start(Util.DebugTime.GENERAL)
var definitions := DefinitionUtil.build_definitions(world_data)
var tileset_overrides := Tileset.get_tileset_overrides(world_data)
Util.timer_finish("Definitions Created")
# Build Tilesets and save as Resources
if Util.options.verbose_output: Util.print("block", "Tilesets")
var tileset_paths := Tileset.build_tilesets(definitions, base_dir, tileset_overrides)
gen_files.append_array(tileset_paths)
# Fetch EntityDef Tile textures
Tileset.get_entity_def_tiles(definitions, Util.tilesets)
# Detect Multi-Worlds
var external_levels: bool = world_data.externalLevels
var world_iid: String = world_data.iid
var world: LDTKWorld
if world_data.worldLayout == null:
var world_nodes: Array[LDTKWorld] = []
var world_instances: Array = world_data.worlds
# Build each world instance
for world_instance in world_instances:
var world_instance_name: String = world_instance.identifier
var world_instance_iid: String = world_instance.iid
var levels := Level.build_levels(world_instance, definitions, base_dir, external_levels)
var world_node := World.create_world(world_instance_name, world_instance_iid, levels, base_dir)
world_nodes.append(world_node)
world = World.create_multi_world(world_name, world_iid, world_nodes)
else:
if Util.options.verbose_output: Util.print("block", "Levels")
var levels := Level.build_levels(world_data, definitions, base_dir, external_levels)
# Save Levels (after Level Post-Import)
if (Util.options.pack_levels):
var levels_path := base_dir + 'levels/'
var directory = DirAccess.open(base_dir)
if not directory.dir_exists(levels_path):
directory.make_dir(levels_path)
# Resolve Refs + Cleanup Resolvers. We don't want to save 'NodePathResolver' in the Level scene.
#if (Util.options.verbose_output): Util.print("block", "References")
if (Util.options.verbose_output): Util.print("block", "Save Levels")
Util.handle_references()
var packed_levels = save_levels(levels, levels_path, gen_files)
if (Util.options.verbose_output): Util.print("block", "Save World")
world = World.create_world(world_name, world_iid, packed_levels, base_dir)
else:
if (Util.options.verbose_output): Util.print("block", "Save World")
world = World.create_world(world_name, world_iid, levels, base_dir)
Util.handle_references()
# Save World as PackedScene
Util.timer_start(Util.DebugTime.SAVE)
var err = save_world(world, save_path, gen_files)
Util.timer_finish("World Saved", 1)
if Util.options.verbose_output: Util.print("block", "Results")
Util.timer_finish("Completed.")
var total_time: int = Util.DebugTime.get_total_time()
var result_message: String = Util.DebugTime.get_result()
if Util.options.verbose_output: Util.print("item_info", result_message)
Util.print("import_finish", str(total_time))
return err
#endregion
func save_world(
world: LDTKWorld,
save_path: String,
gen_files: Array[String]
) -> Error:
var packed_world = PackedScene.new()
packed_world.pack(world)
Util.print("item_save", "Saving World [color=#fe8019][i]'%s'[/i][/color]" % [save_path], 1)
var world_path = "%s.%s" % [save_path, _get_save_extension()]
var err = ResourceSaver.save(packed_world, world_path)
if err == OK:
gen_files.append(world_path)
return err
func save_levels(
levels: Array[LDTKLevel],
save_path: String,
gen_files: Array[String]
) -> Array[LDTKLevel]:
Util.timer_start(Util.DebugTime.SAVE)
var packed_levels: Array[LDTKLevel] = []
var level_names := levels.map(func(elem): return elem.name)
Util.print("item_save", "Saving Levels: [color=#fe8019]%s[/color]" % [level_names], 1)
for level in levels:
for child in level.get_children():
Util.recursive_set_owner(child, level)
var level_path = save_level(level, save_path, gen_files)
var packed_level = load(level_path).instantiate()
packed_levels.append(packed_level)
Util.timer_finish("%s Levels Saved" % [levels.size()], 1)
return packed_levels
func save_level(
level: LDTKLevel,
save_path: String,
gen_files: Array[String]
) -> String:
var packed_level = PackedScene.new()
packed_level.pack(level)
var level_path = "%s%s.%s" % [save_path, level.name, _get_save_extension()]
var err = ResourceSaver.save(packed_level, level_path)
if err == OK:
gen_files.append(level_path)
return level_path