converting datapacks json format to use godot resources instead

This commit is contained in:
Chris Bell 2025-10-21 20:18:33 -05:00
parent 410c532475
commit 2da3b9c6fa
16 changed files with 241 additions and 174 deletions

View File

@ -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

View File

@ -0,0 +1 @@
uid://cu8n3sgejs6it

View File

@ -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)
# ----------------------------------------------------------------------

View File

@ -0,0 +1 @@
uid://bwuvsyn1y3dge

View File

@ -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" % [

View File

@ -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):

View File

@ -0,0 +1,6 @@
class_name DatapackDependency
extends Resource
@export var id: Guid
@export var name: String
@export var version: String

View File

@ -0,0 +1 @@
uid://1eht88nmv63j

View File

@ -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)

View File

@ -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

View File

@ -1 +0,0 @@
uid://hrv66ufdp4no

View File

@ -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] = {}

View File

@ -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:

View File

@ -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]

View File

@ -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]

View File

@ -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