Files
stickman/scripts/props/prop_library.gd

162 lines
6.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:
return _make_custom_entry(path, false)
static func make_visual_entry(path: String) -> Dictionary:
return _make_custom_entry(path, true)
static func _make_custom_entry(path: String, allow_visual_only: bool) -> 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 (not allow_visual_only and 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))
var tags: Array[String] = parsed.get("tags", []) as Array[String]
if tags.is_empty():
tags = _legacy_tags_for_path(normalized_path)
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", {}), "tags": tags, "visual_only": hull.size() < 3}
static func scan_visual_entries(dir_path: String = PROP_FILE.DEFAULT_DIR) -> Array[Dictionary]:
var entries: Array[Dictionary] = []
if not DirAccess.dir_exists_absolute(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: Dictionary = make_visual_entry(path)
if not entry.is_empty():
entries.append(entry)
return entries
static func _legacy_tags_for_path(path: String) -> Array[String]:
var basename: String = path.get_file().get_basename().to_lower()
if basename.contains("tree") or basename.contains("plant"):
return ["nature", "objects"] if basename.contains("plant") else ["nature"]
if basename.contains("skyline") or basename.contains("storefront") or basename.contains("house"):
return ["buildings"]
if basename.contains("window") or basename.contains("fireplace") or basename.contains("clock"):
return ["walls", "objects"]
if basename.contains("bookshelf") or basename.contains("picture") or basename.contains("rug") or basename.contains("couch") or basename.contains("armchair") or basename.contains("table"):
return ["objects"]
return []
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(),
}