115 lines
4.6 KiB
GDScript
115 lines
4.6 KiB
GDScript
class_name PropLibrary
|
|
extends RefCounted
|
|
|
|
const PROP_UTILS := preload("res://scripts/props/prop_utils.gd")
|
|
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
|
|
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
|
|
const PROP_VISUALS := preload("res://scripts/props/prop_visuals.gd")
|
|
const CONTENT_PATHS := preload("res://scripts/shared/content_paths.gd")
|
|
|
|
static var _templates: Array[Dictionary] = []
|
|
|
|
|
|
static func get_entries() -> Array[Dictionary]:
|
|
if _templates.is_empty():
|
|
_build_templates()
|
|
return _templates
|
|
|
|
|
|
static func get_ids() -> Array[String]:
|
|
var ids: Array[String] = []
|
|
for t: Dictionary in get_entries():
|
|
ids.append(String(t["id"]))
|
|
return ids
|
|
|
|
|
|
static func get_entry(id: String) -> Dictionary:
|
|
for t: Dictionary in get_entries():
|
|
if String(t["id"]) == id:
|
|
return t
|
|
return make_custom_entry(id) if is_custom_path(id) else {}
|
|
|
|
|
|
static func scan(dir_path: String = PROP_FILE.DEFAULT_DIR) -> Array[Dictionary]:
|
|
var entries: Array[Dictionary] = get_entries().duplicate(true)
|
|
if not DirAccess.dir_exists_absolute(dir_path):
|
|
push_warning("PropLibrary: prop directory '%s' does not exist." % dir_path)
|
|
return entries
|
|
var directory: DirAccess = DirAccess.open(dir_path)
|
|
if directory == null:
|
|
return entries
|
|
var paths: Array[String] = []
|
|
directory.list_dir_begin()
|
|
var file_name: String = directory.get_next()
|
|
while not file_name.is_empty():
|
|
if not directory.current_is_dir() and file_name.to_lower().ends_with(PROP_FILE.FILE_EXT):
|
|
paths.append(dir_path.path_join(file_name))
|
|
file_name = directory.get_next()
|
|
directory.list_dir_end()
|
|
paths.sort_custom(func(a: String, b: String) -> bool: return a.nocasecmp_to(b) < 0)
|
|
for path: String in paths:
|
|
var entry := make_custom_entry(path)
|
|
if not entry.is_empty(): entries.append(entry)
|
|
entries.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
|
if bool(a.get("is_custom", false)) != bool(b.get("is_custom", false)): return not bool(a.get("is_custom", false))
|
|
var an := String(a.get("name", "")).to_lower(); var bn := String(b.get("name", "")).to_lower()
|
|
return an < bn if an != bn else String(a.get("path", "")).nocasecmp_to(String(b.get("path", ""))) < 0)
|
|
return entries
|
|
|
|
|
|
static func make_custom_entry(path: String) -> Dictionary:
|
|
var normalized_path: String = canonical_path(path)
|
|
if not is_custom_path(normalized_path): return {}
|
|
var raw: Dictionary = PROP_FILE.load(normalized_path)
|
|
if raw.is_empty():
|
|
push_warning("PropLibrary: could not load custom prop '%s'." % normalized_path)
|
|
return {}
|
|
var parsed: Dictionary = PROP_FILE.from_file_dict(raw)
|
|
if parsed.is_empty():
|
|
push_warning("PropLibrary: could not load custom prop '%s'." % normalized_path)
|
|
return {}
|
|
var shapes: Array[Dictionary] = parsed.get("shapes", [])
|
|
var hull: PackedVector2Array = PROP_VISUALS.collision_hull(shapes)
|
|
if shapes.is_empty() or hull.size() < 3:
|
|
push_warning("PropLibrary: custom prop '%s' has no valid closed collision geometry." % normalized_path)
|
|
return {}
|
|
var name := String(parsed.get("prop_name", "")).strip_edges()
|
|
if name.is_empty(): name = normalized_path.get_file().get_basename()
|
|
var preset: int = int(parsed.get("material_preset", PROP_BLOCK.MaterialPreset.NONE))
|
|
return {"id": normalized_path, "path": normalized_path, "name": name, "material_preset": preset, "material_label": PROP_FILE.preset_to_string(preset).capitalize(), "is_custom": true, "payload": PROP_FILE.flatten_to_payload(shapes), "shapes": shapes, "collision_hull": hull, "anchors": parsed.get("anchors", {})}
|
|
|
|
|
|
static func is_custom_path(path: String) -> bool:
|
|
return path.to_lower().ends_with(PROP_FILE.FILE_EXT) and (path.begins_with("res://") or path.begins_with("user://") or path.is_absolute_path())
|
|
|
|
|
|
static func canonical_path(path: String) -> String:
|
|
return CONTENT_PATHS.canonicalize(path.replace("\\", "/").simplify_path())
|
|
|
|
|
|
static func get_default_id() -> String:
|
|
return "crate"
|
|
|
|
|
|
static func _build_templates() -> void:
|
|
_templates = [
|
|
_make("crate", "Crate", PROP_BLOCK.MaterialPreset.WOOD, "Wood", PROP_UTILS.create_box()),
|
|
_make("ball", "Ball", PROP_BLOCK.MaterialPreset.RUBBER, "Rubber", PROP_UTILS.create_ball()),
|
|
_make("plank", "Plank", PROP_BLOCK.MaterialPreset.METAL, "Metal", PROP_UTILS.create_plank()),
|
|
_make("triangle", "Triangle", PROP_BLOCK.MaterialPreset.CARDBOARD, "Cardboard", PROP_UTILS.create_triangle()),
|
|
]
|
|
|
|
|
|
static func _make(id: String, name: String, preset: int, label: String, payload: Dictionary) -> Dictionary:
|
|
return {
|
|
"id": id,
|
|
"name": name,
|
|
"material_preset": preset,
|
|
"material_label": label,
|
|
"payload": payload,
|
|
"is_custom": false,
|
|
"path": "",
|
|
"shapes": [],
|
|
"collision_hull": PackedVector2Array(),
|
|
}
|