diff --git a/sessionzero-client/scripts/helpers/datapack_creator.gd b/sessionzero-client/scripts/helpers/datapack_creator.gd new file mode 100644 index 0000000..40c000b --- /dev/null +++ b/sessionzero-client/scripts/helpers/datapack_creator.gd @@ -0,0 +1,113 @@ +# datapack_creator.gd +class_name DatapackCreator +extends RefCounted + +# --- UPDATED CONSTANTS --- +const BASE_DATAPACKS_PATH = "user://datapacks/" +const CONTENT_SUBDIR = "objects" +const METADATA_FILENAME = "config.szpack" # Renamed from METADATA_FILENAME +const DATAPACK_RESOURCE_FILENAME = "datapack.tres" # Renamed from DATAPACK_RESOURCE_FILENAME +const ASSETS_SUBDIR = "assets" # Renamed from MEDIA_SUBDIR + +# --- INTERNAL STATE --- +var _saved_resources: Dictionary = {} +var _pack_base_path: String = "" + + +# --- PUBLIC METHOD --- + +## Saves a DatapackModel and all its content resources to the file system. +## 'pack_name' is used to create the final folder structure: user://datapacks/[pack_name]/ +func save_datapack(datapack: DatapackModel, pack_name: String) -> bool: + var full_pack_name = pack_name.validate_node_name() + if full_pack_name.is_empty(): + push_error("DatapackCreator: Invalid pack_name provided.") + return false + + _pack_base_path = BASE_DATAPACKS_PATH + full_pack_name + "/" + + var content_path = _pack_base_path + CONTENT_SUBDIR + var assets_path = _pack_base_path + ASSETS_SUBDIR + + var success_dirs = true + success_dirs &= _make_dir_recursive_safe(_pack_base_path) + success_dirs &= _make_dir_recursive_safe(content_path) + success_dirs &= _make_dir_recursive_safe(assets_path) + + if not success_dirs: + push_error("DatapackCreator: Failed to create necessary directories.") + return false + + _saved_resources.clear() + + if not _save_content_objects(datapack.content_objects, content_path + "/"): + return false + + var main_resource_path = _pack_base_path + DATAPACK_RESOURCE_FILENAME + if not _save_single_resource(datapack, main_resource_path): + return false + + if not _write_metadata_file(datapack, _pack_base_path + METADATA_FILENAME): + return false + + print("Datapack saved successfully to: %s" % _pack_base_path) + return true + + +# --- PRIVATE HELPER METHODS --- + +func _make_dir_recursive_safe(path: String) -> bool: + var error = DirAccess.make_dir_recursive_absolute(path) + if error != OK and error != ERR_ALREADY_EXISTS: + push_error("DirAccess: Failed to create directory: %s (Error: %s)" % [path, error]) + return false + return true + + +func _save_content_objects(objects: Array, save_path: String) -> bool: + var success = true + for obj in objects: + if not obj is SzObject: + push_error("DatapackCreator: Content array contains non-SzObject: %s" % obj) + success = false + continue + + # Filename format: [class_name]_[id].tres + var filename = "%s_%s.tres" % [obj.get_class().to_lower(), obj.id.to_lower()] + var full_path = save_path + filename + + if not _save_single_resource(obj, full_path): + success = false + + return success + + +func _save_single_resource(resource: Resource, path: String) -> bool: + if _saved_resources.has(resource): + return true + + var error = ResourceSaver.save(resource, path, ResourceSaver.FLAG_COMPRESS | ResourceSaver.FLAG_CHANGE_PATH) + + if error != OK: + push_error("DatapackCreator: Failed to save resource at path '%s'. Error: %s" % [path, error]) + return false + + _saved_resources[resource] = path + return true + + +func _write_metadata_file(datapack: DatapackModel, path: String) -> bool: + var config = ConfigFile.new() + var section = "Datapack" + + config.set_value(section, "guid", datapack.guid.to_string()) + config.set_value(section, "name", datapack.name) + config.set_value(section, "version", datapack.version) + config.set_value(section, "entry_point", DATAPACK_RESOURCE_FILENAME) + + var error = config.save(path) + if error != OK: + push_error("DatapackCreator: Failed to save metadata file at path '%s'. Error: %s" % [path, error]) + return false + + return true diff --git a/sessionzero-client/scripts/helpers/datapack_creator.gd.uid b/sessionzero-client/scripts/helpers/datapack_creator.gd.uid new file mode 100644 index 0000000..8f0cf69 --- /dev/null +++ b/sessionzero-client/scripts/helpers/datapack_creator.gd.uid @@ -0,0 +1 @@ +uid://cu8n3sgejs6it diff --git a/sessionzero-client/scripts/helpers/datapack_loader.gd b/sessionzero-client/scripts/helpers/datapack_loader.gd new file mode 100644 index 0000000..732e621 --- /dev/null +++ b/sessionzero-client/scripts/helpers/datapack_loader.gd @@ -0,0 +1,55 @@ +# datapack_loader.gd +class_name DatapackLoader +extends RefCounted + +const BASE_DATAPACKS_PATH = "user://datapacks/" +const METADATA_FILENAME = "config.szpack" +const DATAPACK_RESOURCE_FILENAME = "datapack.tres" + +## Loads a DatapackModel resource and all its referenced content from the user://datapacks/ folder. +## Returns the loaded DatapackModel object or null on failure. +func load_datapack(pack_name: String) -> DatapackModel: + var full_pack_name = pack_name.validate_node_name() + if full_pack_name.is_empty(): + push_error("DatapackLoader: Invalid pack_name provided.") + return null + + var pack_dir = BASE_DATAPACKS_PATH + full_pack_name + "/" + var metadata_path = pack_dir + METADATA_FILENAME + + var config = ConfigFile.new() + var error = config.load(metadata_path) + + if error != OK: + push_error("DatapackLoader: Could not load external manifest at: %s (Error: %s)" % [metadata_path, error]) + return null + + var entry_point = config.get_value("Datapack", "entry_point", DATAPACK_RESOURCE_FILENAME) + + var resource_path = pack_dir + entry_point + + if not ResourceLoader.exists(resource_path): + push_error("DatapackLoader: Main Resource file not found at path: %s" % resource_path) + return null + + var datapack: DatapackModel = ResourceLoader.load(resource_path) + + if not datapack: + push_error("DatapackLoader: Failed to load resource object from path: %s" % resource_path) + return null + + datapack._build_content_map() + + print("Successfully loaded Datapack: %s (GUID: %s)" % [datapack.name, datapack.guid.to_string()]) + return datapack + +# ---------------------------------------------------------------------- +# Example Usage: +# var loader = DatapackLoader.new() +# var core_pack = loader.load_datapack("core_rules") +# +# if core_pack: +#\t var item = core_pack.get_object_by_id("sword-long") +#\t if item: +#\t\t print("Found item: " + item.name) +# ---------------------------------------------------------------------- diff --git a/sessionzero-client/scripts/helpers/datapack_loader.gd.uid b/sessionzero-client/scripts/helpers/datapack_loader.gd.uid new file mode 100644 index 0000000..b01a09a --- /dev/null +++ b/sessionzero-client/scripts/helpers/datapack_loader.gd.uid @@ -0,0 +1 @@ +uid://bwuvsyn1y3dge diff --git a/sessionzero-client/scripts/helpers/datetime.gd b/sessionzero-client/scripts/helpers/datetime.gd index 827ea3d..e03dd23 100644 --- a/sessionzero-client/scripts/helpers/datetime.gd +++ b/sessionzero-client/scripts/helpers/datetime.gd @@ -1,19 +1,22 @@ # datetime.gd class_name DateTime -extends RefCounted +extends Resource + +@export var _unix_time: float = 0.0 -var _unix_time: float = 0.0 static func now() -> DateTime: var dt = DateTime.new() dt._unix_time = Time.get_unix_time_from_system() return dt + static func from_unix(unix_seconds: float) -> DateTime: var dt = DateTime.new() dt._unix_time = unix_seconds return dt + static func from_string(input: String) -> DateTime: var s := input.strip_edges() if s.ends_with("Z"): @@ -26,9 +29,11 @@ static func from_string(input: String) -> DateTime: dt._unix_time = unix return dt + func to_unix() -> float: return _unix_time + func _to_string() -> String: var dict := Time.get_datetime_dict_from_unix_time(_unix_time) return "%04d-%02d-%02dT%02d:%02d:%02dZ" % [ diff --git a/sessionzero-client/scripts/helpers/guid.gd b/sessionzero-client/scripts/helpers/guid.gd index 0476af8..fb022a8 100644 --- a/sessionzero-client/scripts/helpers/guid.gd +++ b/sessionzero-client/scripts/helpers/guid.gd @@ -1,7 +1,8 @@ -extends RefCounted class_name Guid +extends Resource + +@export var bytes: PackedByteArray -var bytes: PackedByteArray static func new_guid() -> Guid: var guid = Guid.new() @@ -11,6 +12,7 @@ static func new_guid() -> Guid: guid.bytes[8] = (guid.bytes[8] & 0x3F) | 0x80 return guid + static func from_string(input: String) -> Guid: var clean = input.replace("-", "") if clean.length() != 32: @@ -22,6 +24,7 @@ static func from_string(input: String) -> Guid: guid.bytes.append(clean.substr(i, 2).hex_to_int()) return guid + func _to_string() -> String: var hex = "" for b in bytes: @@ -34,10 +37,11 @@ func _to_string() -> String: hex.substr(20,12) ) + func to_base64() -> String: return Marshalls.raw_to_base64(bytes) -# --- internal --- + static func _generate_random_bytes(length: int) -> PackedByteArray: var arr = PackedByteArray() for i in range(length): diff --git a/sessionzero-client/scripts/models/datapack_dependency.gd b/sessionzero-client/scripts/models/datapack_dependency.gd new file mode 100644 index 0000000..de79c36 --- /dev/null +++ b/sessionzero-client/scripts/models/datapack_dependency.gd @@ -0,0 +1,6 @@ +class_name DatapackDependency +extends Resource + +@export var id: Guid +@export var name: String +@export var version: String diff --git a/sessionzero-client/scripts/models/datapack_dependency.gd.uid b/sessionzero-client/scripts/models/datapack_dependency.gd.uid new file mode 100644 index 0000000..446a9f1 --- /dev/null +++ b/sessionzero-client/scripts/models/datapack_dependency.gd.uid @@ -0,0 +1 @@ +uid://1eht88nmv63j diff --git a/sessionzero-client/scripts/models/datapack_model.gd b/sessionzero-client/scripts/models/datapack_model.gd index cefa6c7..677a406 100644 --- a/sessionzero-client/scripts/models/datapack_model.gd +++ b/sessionzero-client/scripts/models/datapack_model.gd @@ -1,63 +1,35 @@ -extends RefCounted class_name DatapackModel +extends Resource -var guid: Guid -var name: String -var version: String -var author: String -var license: String -var description: String -var icon: String -var created_at: DateTime -var session_zero_version: String -var dependencies: Array[DatapackDependency] +@export var guid: Guid +@export var name: String +@export var version: String +@export var author: String +@export var license: String +@export var description: String +@export var icon: String +@export var created_at: DateTime +@export var session_zero_version: String +@export var dependencies: Array[DatapackDependency] = [] +@export var sz_objects: Array[SzObject] = [] + +var content_map: Dictionary = {} func _init() -> void: - guid = Guid.new_guid() - created_at = DateTime.now() + if guid == null: + guid = Guid.new_guid() + if created_at == null: + created_at = DateTime.now() -func to_dict() -> Dictionary: - var deps_arr: Array = [] - for d in dependencies: - deps_arr.append(d.to_dict()) - return { - "guid": guid.to_string(), - "name": name, - "version": version, - "author": author, - "license": license, - "description": description, - "icon": icon, - "created_at": created_at.to_string(), - "session_zero_version": session_zero_version, - "dependencies": deps_arr - } +func _build_content_map(): + content_map.clear() + for obj in sz_objects: + content_map[obj.id] = obj -# TODO: Implement this -static func from_dict(dict: Dictionary) -> DatapackModel: - var dp: DatapackModel = DatapackModel.new() - dp.name = dict.get("name", "") - dp.version = dict.get("version", "") - dp.author = dict.get("author", "") - dp.license = dict.get("license", "") - dp.description = dict.get("description", "") - dp.icon = dict.get("icon", "") - dp.session_zero_version = dict.get("session_zero_version", "") - - if dict.has("guid"): - dp.guid = Guid.from_string(dict["guid"]) - else: - return null - - dp.dependencies.clear() - if dict.has("dependencies"): - var deps_arr: Array = dict["dependencies"] - - for d_dict in deps_arr: - var dependency: DatapackDependency = DatapackDependency.from_dict(d_dict) - dp.dependencies.append(dependency) - - return dp +func get_object_by_id(obj_id: String) -> SzObject: + if content_map.is_empty() and !sz_objects.is_empty(): + _build_content_map() + return content_map.get(obj_id) diff --git a/sessionzero-client/scripts/models/datapck_dependency.gd b/sessionzero-client/scripts/models/datapck_dependency.gd deleted file mode 100644 index a0e1a57..0000000 --- a/sessionzero-client/scripts/models/datapck_dependency.gd +++ /dev/null @@ -1,24 +0,0 @@ -class_name DatapackDependency -extends RefCounted - -var id: Guid -var name: String -var version: String - - -func to_dict() -> Dictionary: - return { - "id": id.to_string(), - "name": name, - "version": version - } - - -static func from_dict(dict: Dictionary) -> DatapackDependency: - var dd := DatapackDependency.new() - - dd.id = dict.get("id", "") - dd.name = dict.get("name", "") - dd.version = dict.get("version", "") - - return dd diff --git a/sessionzero-client/scripts/models/datapck_dependency.gd.uid b/sessionzero-client/scripts/models/datapck_dependency.gd.uid deleted file mode 100644 index 2547115..0000000 --- a/sessionzero-client/scripts/models/datapck_dependency.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://hrv66ufdp4no diff --git a/sessionzero-client/scripts/models/dataset_model.gd b/sessionzero-client/scripts/models/dataset_model.gd index 9ca35a8..5b58544 100644 --- a/sessionzero-client/scripts/models/dataset_model.gd +++ b/sessionzero-client/scripts/models/dataset_model.gd @@ -1,21 +1,5 @@ class_name DatasetModel extends SzObject -var dataset_type: String -var entries: Dictionary[String, DatasetEntry] - -func to_dict() -> Dictionary: - var dict := super.to_dict() - dict["dataset_type"] = dataset_type - - var entries_dict := {} - if entries: - for key in entries.keys(): - var de := entries[key] - if de != null: - entries_dict[key] = de.to_dict() - else: - entries_dict[key] = null - dict["entries"] = entries_dict - - return dict +@export var dataset_type: String +@export var entries: Dictionary[String, DatasetEntry] = {} diff --git a/sessionzero-client/scripts/models/schema/data_field_value.gd b/sessionzero-client/scripts/models/schema/data_field_value.gd index 8537318..e6528fa 100644 --- a/sessionzero-client/scripts/models/schema/data_field_value.gd +++ b/sessionzero-client/scripts/models/schema/data_field_value.gd @@ -1,5 +1,5 @@ class_name DataFieldValue -extends RefCounted +extends Resource enum DataFieldType { TEXT, @@ -10,8 +10,8 @@ enum DataFieldType { LIST } -var field_type: DataFieldType -var value: Variant +@export var field_type: DataFieldType +@export var value: Variant static func _type_to_string(t: DataFieldType) -> String: match t: diff --git a/sessionzero-client/scripts/models/schema/dataset_entry.gd b/sessionzero-client/scripts/models/schema/dataset_entry.gd index 8759135..2887a61 100644 --- a/sessionzero-client/scripts/models/schema/dataset_entry.gd +++ b/sessionzero-client/scripts/models/schema/dataset_entry.gd @@ -1,32 +1,9 @@ class_name DatasetEntry -extends RefCounted +extends Resource -var id: String -var name: String -var description: String -var icon: String -var top_level_fields: Dictionary[String, DataFieldValue] -var groups: Array[DatasetGroup] - -func to_dict() -> Dictionary: - var groups_array: Array = [] - for g in groups: - groups_array.append(g.to_json()) - - var top_level_fields_dict: Dictionary = {} - if top_level_fields: - for key in top_level_fields.keys(): - var dfv: DataFieldValue = top_level_fields[key] - if dfv != null: - top_level_fields_dict[key] = dfv.to_dict() - else: - top_level_fields_dict[key] = null - - return { - "id": id, - "name": name, - "description": description, - "icon": icon, - "top_level_fields": top_level_fields_dict, - "groups": groups_array - } +@export var id: String +@export var name: String +@export var description: String +@export var icon: String +@export var top_level_fields: Dictionary[String, DataFieldValue] +@export var groups: Array[DatasetGroup] diff --git a/sessionzero-client/scripts/models/schema/dataset_group.gd b/sessionzero-client/scripts/models/schema/dataset_group.gd index e608f19..6a00706 100644 --- a/sessionzero-client/scripts/models/schema/dataset_group.gd +++ b/sessionzero-client/scripts/models/schema/dataset_group.gd @@ -1,23 +1,6 @@ class_name DatasetGroup -extends RefCounted +extends Resource -var id: String -var name: String -var fields: Dictionary[String, DataFieldValue] - - -func to_json() -> Dictionary: - var fields_dict: Dictionary = {} - if fields: - for key in fields.keys(): - var dfv: DataFieldValue = fields[key] - if dfv != null: - fields_dict[key] = dfv.to_dict() - else: - fields_dict[key] = null - - return { - "id": id, - "name": name, - "fields": fields_dict - } +@export var id: String +@export var name: String +@export var fields: Dictionary[String, DataFieldValue] diff --git a/sessionzero-client/scripts/models/szobject.gd b/sessionzero-client/scripts/models/szobject.gd index 2c09dd1..735f2c3 100644 --- a/sessionzero-client/scripts/models/szobject.gd +++ b/sessionzero-client/scripts/models/szobject.gd @@ -1,20 +1,10 @@ class_name SzObject -extends RefCounted +extends Resource -var id: String -var name: String -var sz_type: String -var description: String -var icon: String -var version: String -var schema_version: String - -func to_dict() -> Dictionary: - return { - "id": id, - "name": name, - "description": description, - "icon": icon, - "version": version, - "schema_version": schema_version - } +@export var id: String +@export var name: String +@export var sz_type: String +@export var description: String +@export var icon: String +@export var version: String +@export var schema_version: String