Datapacks system mostly complete
This commit is contained in:
parent
2da3b9c6fa
commit
06c8817996
22
sessionzero-client/datapack_test.tres
Normal file
22
sessionzero-client/datapack_test.tres
Normal file
@ -0,0 +1,22 @@
|
||||
[gd_resource type="Resource" script_class="DatapackModel" load_steps=8 format=3 uid="uid://cuyhpk5xnm735"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://1eht88nmv63j" path="res://scripts/datapacks/models/datapack_dependency.gd" id="1_0qrwo"]
|
||||
[ext_resource type="Script" uid="uid://b2k68if5pcfyn" path="res://scripts/helpers/datetime.gd" id="1_jdefw"]
|
||||
[ext_resource type="Script" uid="uid://dgj5rubcp6v00" path="res://scripts/datapacks/models/datapack_model.gd" id="2_3lhj8"]
|
||||
[ext_resource type="Script" uid="uid://xiy5j06o8254" path="res://scripts/helpers/guid.gd" id="3_8vm13"]
|
||||
[ext_resource type="Script" uid="uid://c8co7n1xvfmou" path="res://scripts/datapacks/models/szobject.gd" id="3_jdefw"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_4o47k"]
|
||||
script = ExtResource("1_jdefw")
|
||||
metadata/_custom_type_script = "uid://b2k68if5pcfyn"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_rn2pn"]
|
||||
script = ExtResource("3_8vm13")
|
||||
metadata/_custom_type_script = "uid://xiy5j06o8254"
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_3lhj8")
|
||||
guid = SubResource("Resource_rn2pn")
|
||||
created_at = SubResource("Resource_4o47k")
|
||||
sz_objects = Array[ExtResource("3_jdefw")]([null])
|
||||
metadata/_custom_type_script = "uid://dgj5rubcp6v00"
|
||||
@ -15,6 +15,10 @@ run/main_scene="uid://fy5iji5t58jk"
|
||||
config/features=PackedStringArray("4.5", "GL Compatibility")
|
||||
config/icon="uid://bxsq8il8lgcq2"
|
||||
|
||||
[autoload]
|
||||
|
||||
DatapackManager="*res://scripts/datapacks/datapack_manager.gd"
|
||||
|
||||
[gui]
|
||||
|
||||
theme/custom="uid://mu3c8g7q4ygp"
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://fy5iji5t58jk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b6xurr0segcug" path="res://scripts/test.gd" id="1_ffwby"]
|
||||
[ext_resource type="Script" uid="uid://b6xurr0segcug" path="res://scripts/datapacks/datapack_tests.gd" id="1_ffwby"]
|
||||
|
||||
[node name="MainUI" type="CanvasLayer"]
|
||||
script = ExtResource("1_ffwby")
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
anchors_preset = 15
|
||||
@ -12,3 +11,6 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_type_variation = &"Background_Panel"
|
||||
|
||||
[node name="DatapackTests" type="Node" parent="."]
|
||||
script = ExtResource("1_ffwby")
|
||||
|
||||
76
sessionzero-client/scripts/datapacks/datapack_manager.gd
Normal file
76
sessionzero-client/scripts/datapacks/datapack_manager.gd
Normal file
@ -0,0 +1,76 @@
|
||||
# datapack_manager.gd
|
||||
extends Node
|
||||
|
||||
var loaded_packs: Dictionary[String, DatapackModel] = {}
|
||||
|
||||
|
||||
func clear_loaded_packs():
|
||||
loaded_packs.clear()
|
||||
|
||||
|
||||
func register_pack(datapack: DatapackModel):
|
||||
if datapack and datapack.guid:
|
||||
var guid_str = datapack.guid.to_string()
|
||||
loaded_packs[guid_str] = datapack
|
||||
print("DatapackManager: Registered pack '%s' (%s)" % [datapack.name, guid_str])
|
||||
else:
|
||||
push_error("DatapackManager: Attempted to register invalid or un-GUIDed datapack.")
|
||||
|
||||
|
||||
func unregister_pack(pack_guid: String):
|
||||
if loaded_packs.has(pack_guid):
|
||||
loaded_packs.erase(pack_guid)
|
||||
print("DatapackManager: Unregistered pack with GUID %s." % pack_guid)
|
||||
|
||||
# --- DEPENDENCY RESOLUTION ---
|
||||
|
||||
## Resolves a single SzObject ID across all loaded packs.
|
||||
## Used by SPECIFIC_DATASET mode when the pack GUID is unknown (or referencing local content).
|
||||
func resolve_object_dependency(object_id: String) -> SzObject:
|
||||
for pack_guid in loaded_packs:
|
||||
var pack = loaded_packs[pack_guid]
|
||||
var obj = pack.get_object_by_id(object_id)
|
||||
|
||||
if obj:
|
||||
return obj
|
||||
|
||||
push_error("Dependency Resolution Failed: Could not find object with ID '%s' in any loaded datapack." % object_id)
|
||||
return null
|
||||
|
||||
|
||||
## Finds all SzObjects (Datasets or Templates) across all loaded packs
|
||||
## that match a given sz_type string.
|
||||
## Used by DATASET_TYPE_WILDCARD and CHARACTER_TEMPLATE_REF modes.
|
||||
func resolve_type_wildcard(sz_type: String) -> Array[SzObject]:
|
||||
var matching_objects: Array[SzObject] = []
|
||||
for pack_guid in loaded_packs:
|
||||
var pack = loaded_packs[pack_guid]
|
||||
|
||||
for obj in pack.sz_objects:
|
||||
if obj.sz_type == sz_type:
|
||||
matching_objects.append(obj)
|
||||
|
||||
if matching_objects.is_empty():
|
||||
push_warning("Wildcard Resolution Failed: Could not find any object with sz_type '%s' in loaded datapacks." % sz_type)
|
||||
|
||||
return matching_objects
|
||||
|
||||
## Main entry point for resolving a DependencySource defined in a Template.
|
||||
## Returns an array of SzObjects (can be Datasets or Templates)
|
||||
func resolve_dependency_source(source: DependencySource) -> Array:
|
||||
match source.mode:
|
||||
DependencySource.DependencyMode.SPECIFIC_DATASET:
|
||||
if source.target_datapack_guid.is_empty():
|
||||
var target_obj = resolve_object_dependency(source.target_sz_object_id)
|
||||
return [target_obj] if target_obj else []
|
||||
|
||||
var target_pack = loaded_packs.get(source.target_datapack_guid)
|
||||
if target_pack:
|
||||
var target_obj = target_pack.get_object_by_id(source.target_sz_object_id)
|
||||
return [target_obj] if target_obj else []
|
||||
return []
|
||||
|
||||
DependencySource.DependencyMode.DATASET_TYPE_WILDCARD:
|
||||
return resolve_type_wildcard(source.target_type_string)
|
||||
|
||||
return []
|
||||
@ -0,0 +1 @@
|
||||
uid://chlpgt0jnkd52
|
||||
374
sessionzero-client/scripts/datapacks/datapack_tests.gd
Normal file
374
sessionzero-client/scripts/datapacks/datapack_tests.gd
Normal file
@ -0,0 +1,374 @@
|
||||
|
||||
|
||||
|
||||
# ------------------------------- WARNING --------------------------------
|
||||
# These tests were auto generated by an LLM according to a design document
|
||||
# to simply test the workflow. These tests should not be completely relied
|
||||
# on for tesing full functionality of the datapack system.
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
|
||||
class_name DatapackTestNode
|
||||
extends Node
|
||||
|
||||
# --- PRIMARY PACK CONSTANTS ---
|
||||
const PACK_NAME = "core_data_test_pack"
|
||||
const ENTRY_ID = "test_item_sword"
|
||||
const WEAPONS_DATASET_ID = "weapons_dataset"
|
||||
const SKILLS_DATASET_ID = "skills_dataset"
|
||||
const CHAR_TEMPLATE_ID = "base_char_template"
|
||||
const SESSION_TEMPLATE_ID = "basic_session_template"
|
||||
|
||||
# --- NEW CROSS-PACK CONSTANTS ---
|
||||
const DEPENDENCY_PACK_NAME = "stat_core_pack"
|
||||
const DEPENDENCY_DATASET_ID = "core_stats_dataset"
|
||||
const DEPENDENCY_ENTRY_ID = "base_strength"
|
||||
const DEPENDER_PACK_NAME = "class_pack"
|
||||
const DEPENDER_DATASET_ID = "warrior_class"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
||||
print("--- Starting Datapack Test ---")
|
||||
|
||||
# --- Primary Test (Pack 1) ---
|
||||
var creation_success = _create_and_save_datapack()
|
||||
if not creation_success:
|
||||
push_error("TEST FAILED: Datapack creation failed.")
|
||||
return
|
||||
|
||||
_load_and_verify_datapack()
|
||||
_test_formula_resolution()
|
||||
|
||||
# --- New Cross-Pack Dependency Test (Pack 2 & 3) ---
|
||||
_test_cross_datapack_dependency()
|
||||
|
||||
print("--- Datapack Test Complete ---")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
## --- STEP 2: CREATION AND SAVING ---
|
||||
#----------------------------------------------------------------------
|
||||
func _create_and_save_datapack() -> bool:
|
||||
print("\n--- 2. Creating and Saving Datapack ---")
|
||||
|
||||
# --- 0. Setup Core Data ---
|
||||
var stats_group := DatasetGroup.new()
|
||||
stats_group.id = "base_stats"
|
||||
stats_group.name = "Base Stats"
|
||||
|
||||
# Simple Numeric Field
|
||||
var damage_field := DataFieldValue.new()
|
||||
damage_field.field_type = DataFieldValue.DataFieldType.NUMBER
|
||||
damage_field.value = 12
|
||||
stats_group.fields["damage"] = damage_field
|
||||
|
||||
# --- FORMULA Field ---
|
||||
var formula_res := FormulaValue.new()
|
||||
formula_res.expression = "Base_Damage * Critical_Multiplier"
|
||||
|
||||
var crit_damage_field := DataFieldValue.new()
|
||||
crit_damage_field.field_type = DataFieldValue.DataFieldType.FORMULA
|
||||
crit_damage_field.value = formula_res
|
||||
stats_group.fields["critical_damage"] = crit_damage_field
|
||||
|
||||
# --- LIST Field ---
|
||||
var list_res := ListValue.new()
|
||||
|
||||
# Add nested DataFieldValue items to the ListValue resource
|
||||
var list_item_1 := DataFieldValue.new()
|
||||
list_item_1.field_type = DataFieldValue.DataFieldType.NUMBER
|
||||
list_item_1.value = 5
|
||||
|
||||
var list_item_2 := DataFieldValue.new()
|
||||
list_item_2.field_type = DataFieldValue.DataFieldType.TEXT
|
||||
list_item_2.value = "Stun"
|
||||
|
||||
list_res.items.append(list_item_1)
|
||||
list_res.items.append(list_item_2)
|
||||
|
||||
var effect_list_field := DataFieldValue.new()
|
||||
effect_list_field.field_type = DataFieldValue.DataFieldType.LIST
|
||||
effect_list_field.value = list_res
|
||||
stats_group.fields["on_hit_effects"] = effect_list_field
|
||||
|
||||
# Create Dataset Entry
|
||||
var sword_entry := DatasetEntry.new()
|
||||
sword_entry.id = ENTRY_ID
|
||||
sword_entry.name = "Longsword of Testing"
|
||||
sword_entry.description = "A sword for unit testing."
|
||||
sword_entry.groups.append(stats_group)
|
||||
|
||||
# Create Dataset Model (Weapons)
|
||||
var weapons_dataset := DatasetModel.new()
|
||||
weapons_dataset.id = WEAPONS_DATASET_ID
|
||||
weapons_dataset.name = "Weapons"
|
||||
weapons_dataset.entries[sword_entry.id] = sword_entry
|
||||
weapons_dataset.sz_type = "Weapon"
|
||||
|
||||
# --- Dependency Setup: Skills Dataset ---
|
||||
var skills_dataset := DatasetModel.new()
|
||||
skills_dataset.id = SKILLS_DATASET_ID
|
||||
skills_dataset.name = "Skills"
|
||||
skills_dataset.sz_type = "Skill"
|
||||
|
||||
# --- Dependency Setup: CharacterTemplate ---
|
||||
var char_template := CharacterTemplate.new()
|
||||
char_template.id = CHAR_TEMPLATE_ID
|
||||
char_template.name = "Standard Character"
|
||||
|
||||
var skills_dependency := DependencySource.new()
|
||||
skills_dependency.mode = DependencySource.DependencyMode.SPECIFIC_DATASET
|
||||
skills_dependency.target_sz_object_id = SKILLS_DATASET_ID
|
||||
|
||||
char_template.data_dependencies["Skills_Source"] = skills_dependency
|
||||
|
||||
# --- Dependency Setup: SessionTemplate ---
|
||||
var session_template := SessionTemplate.new()
|
||||
session_template.id = SESSION_TEMPLATE_ID
|
||||
session_template.name = "Basic Campaign Session"
|
||||
|
||||
var char_dep_source := DependencySource.new()
|
||||
char_dep_source.mode = DependencySource.DependencyMode.SPECIFIC_DATASET
|
||||
char_dep_source.target_sz_object_id = CHAR_TEMPLATE_ID
|
||||
session_template.character_template_ref = char_dep_source
|
||||
|
||||
# --- Create and Save Datapack Model ---
|
||||
var datapack := DatapackModel.new()
|
||||
datapack.name = "Test Pack"
|
||||
datapack.version = "1.0.0"
|
||||
|
||||
datapack.sz_objects.append(weapons_dataset)
|
||||
datapack.sz_objects.append(skills_dataset)
|
||||
datapack.sz_objects.append(char_template)
|
||||
datapack.sz_objects.append(session_template)
|
||||
|
||||
var creator = DatapackCreator.new()
|
||||
var success = creator.save_datapack(datapack, PACK_NAME)
|
||||
|
||||
return success
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
## --- STEP 3: LOADING AND VERIFICATION (Omitted for brevity, assumed stable) ---
|
||||
#----------------------------------------------------------------------
|
||||
func _load_and_verify_datapack() -> void:
|
||||
print("\n--- 3. Loading and Verification ---")
|
||||
var loader = DatapackLoader.new()
|
||||
|
||||
var loaded_pack: DatapackModel = loader.load_datapack(PACK_NAME)
|
||||
|
||||
if not loaded_pack:
|
||||
push_error("TEST FAILED: DatapackLoader returned null.")
|
||||
return
|
||||
|
||||
DatapackManager.register_pack(loaded_pack)
|
||||
print("Verification: Datapack registered with DatapackManager.")
|
||||
|
||||
var loaded_weapons_dataset: DatasetModel = loaded_pack.get_object_by_id(WEAPONS_DATASET_ID)
|
||||
var loaded_entry: DatasetEntry = loaded_weapons_dataset.entries.get(ENTRY_ID)
|
||||
var loaded_group: DatasetGroup = loaded_entry.groups.filter(func(g): return g.id == "base_stats").front()
|
||||
var loaded_char_template: CharacterTemplate = loaded_pack.get_object_by_id(CHAR_TEMPLATE_ID)
|
||||
var loaded_session_template: SessionTemplate = loaded_pack.get_object_by_id(SESSION_TEMPLATE_ID)
|
||||
|
||||
# 1. SessionTemplate Character Reference
|
||||
var char_ref_dep_source: DependencySource = loaded_session_template.character_template_ref
|
||||
var resolved_char_template_array = DatapackManager.resolve_dependency_source(char_ref_dep_source)
|
||||
if resolved_char_template_array.size() == 1 and resolved_char_template_array.front() == loaded_char_template:
|
||||
print("✅ SUCCESS: SessionTemplate dependency link resolved.")
|
||||
else:
|
||||
push_error("TEST FAILED: SessionTemplate character_template_ref resolution failed.")
|
||||
|
||||
# 2. CharacterTemplate Skills Dependency
|
||||
var skills_dep_source: DependencySource = loaded_char_template.data_dependencies.get("Skills_Source")
|
||||
var resolved_skills_array = DatapackManager.resolve_dependency_source(skills_dep_source)
|
||||
var loaded_skills_dataset = loaded_pack.get_object_by_id(SKILLS_DATASET_ID)
|
||||
if resolved_skills_array.size() == 1 and resolved_skills_array.front() == loaded_skills_dataset:
|
||||
print("✅ SUCCESS: CharacterTemplate data dependency link resolved.")
|
||||
else:
|
||||
push_error("TEST FAILED: CharacterTemplate data dependency resolution failed.")
|
||||
|
||||
# 3. Verify FORMULA field
|
||||
var loaded_crit_field: DataFieldValue = loaded_group.fields.get("critical_damage")
|
||||
if loaded_crit_field and loaded_crit_field.field_type == DataFieldValue.DataFieldType.FORMULA:
|
||||
var formula: FormulaValue = loaded_crit_field.value
|
||||
if formula is FormulaValue and formula.expression == "Base_Damage * Critical_Multiplier":
|
||||
print("✅ SUCCESS: Found and verified FormulaValue resource.")
|
||||
else:
|
||||
push_error("TEST FAILED: FormulaValue verification failed (wrong type or expression).")
|
||||
else:
|
||||
push_error("TEST FAILED: Critical damage field not found or wrong type.")
|
||||
|
||||
# 4. Verify LIST field
|
||||
var loaded_list_field: DataFieldValue = loaded_group.fields.get("on_hit_effects")
|
||||
if loaded_list_field and loaded_list_field.field_type == DataFieldValue.DataFieldType.LIST:
|
||||
var list_val: ListValue = loaded_list_field.value
|
||||
if list_val is ListValue and list_val.items.size() == 2:
|
||||
if list_val.items[1] is DataFieldValue and list_val.items[1].value == "Stun":
|
||||
print("✅ SUCCESS: Found and verified ListValue and its nested contents.")
|
||||
else:
|
||||
push_error("TEST FAILED: ListValue contents verification failed.")
|
||||
else:
|
||||
push_error("TEST FAILED: ListValue resource failed to load correctly.")
|
||||
else:
|
||||
push_error("TEST FAILED: On hit effects field not found or wrong type.")
|
||||
|
||||
print("\nVerification: All checks completed.")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
## --- STEP 4: FORMULA RESOLUTION TEST (Omitted for brevity, assumed stable) ---
|
||||
#----------------------------------------------------------------------
|
||||
func _test_formula_resolution():
|
||||
print("\n--- 4. Testing Formula Resolver with Variable Substitution ---")
|
||||
|
||||
var resolver := FormulaResolver.new()
|
||||
var raw_expression := "Base_Damage * Critical_Multiplier + floor(Skill_Level / 2)"
|
||||
|
||||
var context: Dictionary = {
|
||||
"Base_Damage": 15,
|
||||
"Critical_Multiplier": 2.5,
|
||||
"Skill_Level": 7
|
||||
}
|
||||
|
||||
var expected_result = 40.5
|
||||
var result = resolver.resolve_formula(raw_expression, context)
|
||||
|
||||
if abs(result - expected_result) < 0.001:
|
||||
print("✅ SUCCESS: FormulaResolver calculated the correct result: %f" % result)
|
||||
else:
|
||||
push_error("TEST FAILED: FormulaResolver result incorrect. Expected %f, Got %f" % [expected_result, result])
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
## --- STEP 5: CROSS-PACK DEPENDENCY RESOLUTION ---
|
||||
#----------------------------------------------------------------------
|
||||
func _test_cross_datapack_dependency():
|
||||
print("\n--- 5. Testing Cross-Pack Dependency Resolution ---")
|
||||
|
||||
DatapackManager.clear_loaded_packs()
|
||||
print("DatapackManager cleared for cross-pack test.")
|
||||
|
||||
# --- 5a. Create and Save DEPENDENCY PACK (Pack A: Stat Core) ---
|
||||
# FIX: Capture the DatapackModel to get its GUID
|
||||
var pack_a_model: DatapackModel = _create_dependency_pack_a()
|
||||
if not pack_a_model:
|
||||
push_error("TEST FAILED: Failed to create Dependency Pack A.")
|
||||
return
|
||||
|
||||
var dependency_pack_guid: Guid = pack_a_model.guid # <--- This is the Guid object needed
|
||||
print("Pack A GUID captured: %s" % dependency_pack_guid.to_string())
|
||||
|
||||
# --- 5b. Create and Save DEPENDER PACK (Pack B: Class Pack) ---
|
||||
# FIX: Pass the GUID object to the creator function
|
||||
var success_b = _create_depender_pack_b(dependency_pack_guid)
|
||||
if not success_b:
|
||||
push_error("TEST FAILED: Failed to create Depender Pack B.")
|
||||
return
|
||||
|
||||
# --- 5c. Load and Register Both Packs ---
|
||||
var loader = DatapackLoader.new()
|
||||
var pack_a: DatapackModel = loader.load_datapack(DEPENDENCY_PACK_NAME)
|
||||
var pack_b: DatapackModel = loader.load_datapack(DEPENDER_PACK_NAME)
|
||||
|
||||
if not pack_a or not pack_b:
|
||||
push_error("TEST FAILED: Failed to load one or both dependency test packs.")
|
||||
return
|
||||
|
||||
DatapackManager.register_pack(pack_a)
|
||||
DatapackManager.register_pack(pack_b)
|
||||
print("Registered both Dependency (A) and Depender (B) packs.")
|
||||
|
||||
# --- 5d. Resolve Dependency in Pack B ---
|
||||
var depender_dataset: DatasetModel = pack_b.get_object_by_id(DEPENDER_DATASET_ID)
|
||||
if not depender_dataset:
|
||||
push_error("TEST FAILED: Could not find Depender Dataset in Pack B.")
|
||||
return
|
||||
|
||||
var dependency_source: DependencySource = depender_dataset.get_meta("core_stats_source")
|
||||
if not dependency_source:
|
||||
push_error("TEST FAILED: Could not find the DependencySource object in Pack B.")
|
||||
return
|
||||
|
||||
var resolved_objects: Array = DatapackManager.resolve_dependency_source(dependency_source)
|
||||
|
||||
# --- 5e. Verification ---
|
||||
if resolved_objects.size() != 1:
|
||||
push_error("TEST FAILED: Dependency resolution returned incorrect count: %d" % resolved_objects.size())
|
||||
return
|
||||
|
||||
var resolved_dataset: DatasetModel = resolved_objects.front()
|
||||
var expected_dataset: DatasetModel = pack_a.get_object_by_id(DEPENDENCY_DATASET_ID)
|
||||
|
||||
if resolved_dataset == expected_dataset:
|
||||
print("✅ SUCCESS: Cross-pack dependency successfully resolved.")
|
||||
if resolved_dataset.entries.has(DEPENDENCY_ENTRY_ID):
|
||||
print("✅ SUCCESS: Resolved dataset contains the expected entry '%s'." % DEPENDENCY_ENTRY_ID)
|
||||
else:
|
||||
push_error("TEST FAILED: Dependency resolved to the wrong object.")
|
||||
|
||||
|
||||
# --- Helper Functions for Cross-Pack Test ---
|
||||
|
||||
# UPDATED: Returns the DatapackModel for GUID retrieval
|
||||
func _create_dependency_pack_a() -> DatapackModel:
|
||||
# Create entry
|
||||
var str_field := DataFieldValue.new()
|
||||
str_field.field_type = DataFieldValue.DataFieldType.NUMBER
|
||||
str_field.value = 10
|
||||
|
||||
var strength_entry := DatasetEntry.new()
|
||||
strength_entry.id = DEPENDENCY_ENTRY_ID
|
||||
strength_entry.name = "Strength"
|
||||
strength_entry.top_level_fields["value"] = str_field
|
||||
|
||||
# Create dataset
|
||||
var core_stats_dataset := DatasetModel.new()
|
||||
core_stats_dataset.id = DEPENDENCY_DATASET_ID
|
||||
core_stats_dataset.name = "Core Stats"
|
||||
core_stats_dataset.sz_type = "StatDefinition"
|
||||
core_stats_dataset.entries[strength_entry.id] = strength_entry
|
||||
|
||||
# Create datapack
|
||||
var datapack := DatapackModel.new()
|
||||
datapack.name = "Stat Core Pack"
|
||||
datapack.version = "1.0.0"
|
||||
datapack.sz_objects.append(core_stats_dataset)
|
||||
|
||||
var creator = DatapackCreator.new()
|
||||
var success = creator.save_datapack(datapack, DEPENDENCY_PACK_NAME)
|
||||
|
||||
if success:
|
||||
return datapack
|
||||
else:
|
||||
return null # Return null on failure
|
||||
|
||||
# UPDATED: Accepts the GUID to correctly create the DatapackDependency
|
||||
func _create_depender_pack_b(dependency_pack_guid: Guid) -> bool:
|
||||
# Create DependencySource (internal reference by string ID)
|
||||
var dependency_source := DependencySource.new()
|
||||
dependency_source.mode = DependencySource.DependencyMode.SPECIFIC_DATASET
|
||||
dependency_source.target_sz_object_id = DEPENDENCY_DATASET_ID
|
||||
|
||||
# Create Depender Dataset
|
||||
var warrior_class_dataset := DatasetModel.new()
|
||||
warrior_class_dataset.id = DEPENDER_DATASET_ID
|
||||
warrior_class_dataset.name = "Warrior Class"
|
||||
warrior_class_dataset.sz_type = "ClassDefinition"
|
||||
|
||||
warrior_class_dataset.set_meta("core_stats_source", dependency_source)
|
||||
|
||||
# Create Datapack
|
||||
var datapack := DatapackModel.new()
|
||||
datapack.name = "Warrior Class Pack"
|
||||
datapack.version = "1.0.0"
|
||||
datapack.sz_objects.append(warrior_class_dataset)
|
||||
|
||||
# Add the explicit pack dependency
|
||||
var pack_dep := DatapackDependency.new()
|
||||
# FIX: Assign the Guid object to the 'id' property
|
||||
pack_dep.id = dependency_pack_guid
|
||||
pack_dep.name = "Stat Core Pack"
|
||||
pack_dep.version = "1.0.0"
|
||||
datapack.dependencies.append(pack_dep)
|
||||
|
||||
var creator = DatapackCreator.new()
|
||||
var success = creator.save_datapack(datapack, DEPENDER_PACK_NAME)
|
||||
return success
|
||||
@ -0,0 +1,13 @@
|
||||
# character_template.gd
|
||||
class_name CharacterTemplate
|
||||
extends SzObject
|
||||
# sz_type will be set to "CharacterTemplate"
|
||||
|
||||
# Core definitions for character sheet fields
|
||||
@export var top_level_field_definitions: Dictionary = {} # TemplateFieldDefinition
|
||||
@export var group_definitions: Array[TemplateGroup] = []
|
||||
|
||||
# --- SPECIALIZED DEPENDENCIES ---
|
||||
# The Character Template needs to reference *Datasets* (like Weapons, Skills, etc.)
|
||||
# Map of logical_name (e.g., "Weapon_Source") -> DependencySource (the new class below)
|
||||
@export var data_dependencies: Dictionary[String, DependencySource] = {}
|
||||
@ -0,0 +1 @@
|
||||
uid://5k8j3av0kqg4
|
||||
@ -0,0 +1,13 @@
|
||||
# session_template.gd
|
||||
class_name SessionTemplate
|
||||
extends SzObject
|
||||
|
||||
# Core definitions for session fields (e.g., initial currency, setting notes)
|
||||
@export var top_level_field_definitions: Dictionary = {}
|
||||
@export var group_definitions: Array[TemplateGroup] = []
|
||||
|
||||
@export var character_template_ref: DependencySource
|
||||
@export var data_dependencies: Dictionary[String, DependencySource] = {}
|
||||
|
||||
func _init() -> void:
|
||||
sz_type = "session_template"
|
||||
@ -0,0 +1 @@
|
||||
uid://dnjuh1ypyoie8
|
||||
@ -0,0 +1,7 @@
|
||||
# template_model.gd
|
||||
class_name TemplateModel
|
||||
extends SzObject
|
||||
|
||||
@export var top_level_field_definitions: Dictionary = {}
|
||||
@export var group_definitions: Array[TemplateGroup] = []
|
||||
@export var data_dependencies: Dictionary[String, String] = {}
|
||||
@ -0,0 +1 @@
|
||||
uid://bd60mhmrr4olw
|
||||
@ -7,12 +7,16 @@ enum DataFieldType {
|
||||
NUMBER,
|
||||
BOOL,
|
||||
FORMULA,
|
||||
LIST
|
||||
LIST,
|
||||
REFERENCE_SINGLE,
|
||||
REFERENCE_LIST
|
||||
}
|
||||
|
||||
@export var field_type: DataFieldType
|
||||
@export var value: Variant
|
||||
|
||||
# --- TYPE STRING MAPPING ---
|
||||
|
||||
static func _type_to_string(t: DataFieldType) -> String:
|
||||
match t:
|
||||
DataFieldType.TEXT: return "Text"
|
||||
@ -21,6 +25,8 @@ static func _type_to_string(t: DataFieldType) -> String:
|
||||
DataFieldType.BOOL: return "Boolean"
|
||||
DataFieldType.FORMULA: return "Formula"
|
||||
DataFieldType.LIST: return "List"
|
||||
DataFieldType.REFERENCE_SINGLE: return "RefSingle"
|
||||
DataFieldType.REFERENCE_LIST: return "RefList"
|
||||
_: return str(int(t))
|
||||
|
||||
static func _string_to_type(s: String) -> DataFieldType:
|
||||
@ -31,8 +37,23 @@ static func _string_to_type(s: String) -> DataFieldType:
|
||||
"Boolean": return DataFieldType.BOOL
|
||||
"Formula": return DataFieldType.FORMULA
|
||||
"List": return DataFieldType.LIST
|
||||
"RefSingle": return DataFieldType.REFERENCE_SINGLE
|
||||
"RefList": return DataFieldType.REFERENCE_LIST
|
||||
_: return DataFieldType.TEXT
|
||||
|
||||
# --- COMPLEX VALUE ACCESS ---
|
||||
|
||||
func get_concrete_value() -> Variant:
|
||||
match field_type:
|
||||
DataFieldType.FORMULA:
|
||||
return value as FormulaValue
|
||||
DataFieldType.LIST, DataFieldType.REFERENCE_LIST:
|
||||
return value as ListValue
|
||||
_:
|
||||
return value
|
||||
|
||||
# --- SERIALIZATION HELPERS ---
|
||||
|
||||
static func _envelope_variant(v: Variant) -> Dictionary:
|
||||
return {
|
||||
"_kind": "gd-variant-b64",
|
||||
@ -63,6 +84,8 @@ static func _deserialize_list_value(a: Array) -> Array:
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
# --- SERIALIZE/DESERIALIZE VALUE ---
|
||||
|
||||
static func serialize_value(p_field_type: DataFieldType, v: Variant) -> Variant:
|
||||
match p_field_type:
|
||||
DataFieldType.TEXT, DataFieldType.MULTILINE_TEXT:
|
||||
@ -77,10 +100,6 @@ static func serialize_value(p_field_type: DataFieldType, v: Variant) -> Variant:
|
||||
return int(s)
|
||||
DataFieldType.BOOL:
|
||||
return bool(v)
|
||||
DataFieldType.FORMULA:
|
||||
return str(v)
|
||||
DataFieldType.LIST:
|
||||
return _serialize_list_value(v)
|
||||
_:
|
||||
if v is Dictionary or v is Array or v is String or v is int or v is float or v is bool or v == null:
|
||||
return v
|
||||
@ -94,15 +113,13 @@ static func deserialize_value(p_field_type: DataFieldType, raw: Variant) -> Vari
|
||||
return raw if (raw is float or raw is int) else float(str(raw))
|
||||
DataFieldType.BOOL:
|
||||
return bool(raw)
|
||||
DataFieldType.FORMULA:
|
||||
return str(raw)
|
||||
DataFieldType.LIST:
|
||||
return _deserialize_list_value(raw if raw is Array else [])
|
||||
_:
|
||||
if raw is Dictionary and raw.get("_kind") == "gd-variant-b64":
|
||||
return _deenvelope_variant(raw)
|
||||
return raw
|
||||
|
||||
# --- DICT CONVERSION ---
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"field_type": _type_to_string(field_type),
|
||||
@ -0,0 +1,20 @@
|
||||
# dependency_source.gd
|
||||
class_name DependencySource
|
||||
extends Resource
|
||||
|
||||
enum DependencyMode {
|
||||
SPECIFIC_DATASET, # Reference a known Dataset/Template by its ID and GUID
|
||||
DATASET_TYPE_WILDCARD, # Reference ANY Dataset/Template that matches a specific type string
|
||||
}
|
||||
|
||||
@export var mode: DependencyMode = DependencyMode.SPECIFIC_DATASET
|
||||
@export var display_name: String = ""
|
||||
|
||||
# --- Used for SPECIFIC_DATASET mode ---
|
||||
# The GUID of the Datapack the dependency lives in (optional, can be empty for local pack)
|
||||
@export var target_datapack_guid: String = ""
|
||||
@export var target_sz_object_id: String = ""
|
||||
|
||||
# --- Used for DATASET_TYPE_WILDCARD mode ---
|
||||
# The type string to match (e.g., "items").
|
||||
@export var target_type_string: String = ""
|
||||
@ -0,0 +1 @@
|
||||
uid://cly4hb2wrx8fn
|
||||
@ -0,0 +1,52 @@
|
||||
# formula_resolver.gd
|
||||
class_name FormulaResolver
|
||||
extends RefCounted
|
||||
|
||||
var _expression: Expression = Expression.new()
|
||||
|
||||
## Internal helper to replace named variables with their numerical values.
|
||||
## NOTE: This simple approach sorts keys by length descending (Max_HP before HP)
|
||||
## to prevent incorrect substitution of substrings.
|
||||
func _substitute_variables(raw_expression: String, context: Dictionary) -> String:
|
||||
var result = raw_expression
|
||||
|
||||
# Sort keys by length descending to prevent shorter names (like 'HP') from matching
|
||||
# before longer names that contain them (like 'Max_HP').
|
||||
var keys = context.keys()
|
||||
keys.sort_custom(func(a, b): return len(a) > len(b))
|
||||
|
||||
for key in keys:
|
||||
if raw_expression.find(key) != -1:
|
||||
var value = str(context[key])
|
||||
# Replace the variable name with its numerical value string
|
||||
result = result.replace(key, value)
|
||||
|
||||
return result
|
||||
|
||||
## Resolves the final value of the formula given a set of variable values.
|
||||
## Handles variable substitution, parsing, and execution in one step.
|
||||
func resolve_formula(raw_expression: String, context: Dictionary) -> float:
|
||||
# 1. Substitute variables with their numerical values
|
||||
var numerical_expression = _substitute_variables(raw_expression, context)
|
||||
|
||||
# 2. Parse the new numerical expression
|
||||
# The expression must be re-parsed every time, as its text content changes.
|
||||
var error: Error = _expression.parse(numerical_expression, PackedStringArray([]))
|
||||
|
||||
if error != OK:
|
||||
push_error("FormulaResolver: Substitution/Parse failed. Expression: '%s'. Numerical: '%s'. Error: %s" % [raw_expression, numerical_expression, _expression.get_error_text()])
|
||||
return 0.0
|
||||
|
||||
# 3. Execute the expression (inputs: empty array, base_instance: null, show_error: true)
|
||||
var result = _expression.execute([], null, true)
|
||||
|
||||
if _expression.has_execute_failed():
|
||||
push_error("FormulaResolver: Execution failed for numerical expression: %s" % numerical_expression)
|
||||
return 0.0
|
||||
|
||||
# 4. Check type and return
|
||||
if typeof(result) != TYPE_FLOAT and typeof(result) != TYPE_INT:
|
||||
push_error("FormulaResolver: Expression result was not a number. Got type: %s" % typeof(result))
|
||||
return 0.0
|
||||
|
||||
return float(result)
|
||||
@ -0,0 +1 @@
|
||||
uid://ils0nw5fyh7j
|
||||
10
sessionzero-client/scripts/datapacks/other/formula_value.gd
Normal file
10
sessionzero-client/scripts/datapacks/other/formula_value.gd
Normal file
@ -0,0 +1,10 @@
|
||||
# formula_value.gd
|
||||
class_name FormulaValue
|
||||
extends Resource
|
||||
|
||||
# The raw expression string (e.g., "Attack_Modifier + Weapon_Damage_Dice")
|
||||
@export var expression: String = ""
|
||||
@export var variables: Dictionary = {}
|
||||
|
||||
# need a separate system (a FormulaEvaluator) later to parse and execute this.
|
||||
# For now, this resource simply stores the definition.
|
||||
@ -0,0 +1 @@
|
||||
uid://cyij11ojm1ecy
|
||||
5
sessionzero-client/scripts/datapacks/other/list_value.gd
Normal file
5
sessionzero-client/scripts/datapacks/other/list_value.gd
Normal file
@ -0,0 +1,5 @@
|
||||
# list_value.gd
|
||||
class_name ListValue
|
||||
extends Resource
|
||||
|
||||
@export var items: Array[DataFieldValue] = []
|
||||
@ -0,0 +1 @@
|
||||
uid://cpdx3w11rbt5j
|
||||
22
sessionzero-client/scripts/datapacks/other/template_field.gd
Normal file
22
sessionzero-client/scripts/datapacks/other/template_field.gd
Normal file
@ -0,0 +1,22 @@
|
||||
# template_field_definition.gd
|
||||
class_name TemplateField
|
||||
extends Resource
|
||||
|
||||
enum DataFieldType {
|
||||
TEXT,
|
||||
MULTILINE_TEXT,
|
||||
NUMBER,
|
||||
BOOL,
|
||||
FORMULA,
|
||||
LIST,
|
||||
REFERENCE_SINGLE,
|
||||
REFERENCE_LIST
|
||||
}
|
||||
|
||||
@export var field_id: String
|
||||
@export var display_name: String
|
||||
@export var field_type: DataFieldType = DataFieldType.TEXT
|
||||
@export var default_value: Variant
|
||||
|
||||
@export var dependency_source_name: String = ""
|
||||
@export var dependency_source_key: String = ""
|
||||
@ -0,0 +1 @@
|
||||
uid://b4d6mtffupbk3
|
||||
@ -0,0 +1,8 @@
|
||||
# template_group.gd
|
||||
class_name TemplateGroup
|
||||
extends Resource
|
||||
|
||||
@export var name: String
|
||||
@export var group_id: String
|
||||
@export var field_definitions: Dictionary = {}
|
||||
@export var is_list: bool = false
|
||||
@ -0,0 +1 @@
|
||||
uid://h3ptq7tj3tad
|
||||
@ -2,22 +2,16 @@
|
||||
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
|
||||
const METADATA_FILENAME = "config.szpack"
|
||||
const DATAPACK_RESOURCE_FILENAME = "datapack.res"
|
||||
const ASSETS_SUBDIR = "assets"
|
||||
|
||||
const SINGLE_FILE_SAVE_FLAGS = ResourceSaver.FLAG_COMPRESS | ResourceSaver.FLAG_CHANGE_PATH
|
||||
|
||||
# --- 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():
|
||||
@ -26,25 +20,22 @@ func save_datapack(datapack: DatapackModel, pack_name: String) -> bool:
|
||||
|
||||
_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)
|
||||
success_dirs = success_dirs and _make_dir_recursive_safe(_pack_base_path)
|
||||
success_dirs = success_dirs and _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):
|
||||
|
||||
var error = ResourceSaver.save(datapack, main_resource_path, SINGLE_FILE_SAVE_FLAGS)
|
||||
|
||||
if error != OK:
|
||||
push_error("DatapackCreator: Failed to save datapack resource at path '%s'. Error: %s" % [main_resource_path, error])
|
||||
return false
|
||||
|
||||
if not _write_metadata_file(datapack, _pack_base_path + METADATA_FILENAME):
|
||||
@ -54,48 +45,14 @@ func save_datapack(datapack: DatapackModel, pack_name: String) -> bool:
|
||||
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:
|
||||
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"
|
||||
|
||||
@ -4,7 +4,8 @@ extends RefCounted
|
||||
|
||||
const BASE_DATAPACKS_PATH = "user://datapacks/"
|
||||
const METADATA_FILENAME = "config.szpack"
|
||||
const DATAPACK_RESOURCE_FILENAME = "datapack.tres"
|
||||
# CHANGE HERE
|
||||
const DATAPACK_RESOURCE_FILENAME = "datapack.res"
|
||||
|
||||
## Loads a DatapackModel resource and all its referenced content from the user://datapacks/ folder.
|
||||
## Returns the loaded DatapackModel object or null on failure.
|
||||
@ -32,6 +33,7 @@ func load_datapack(pack_name: String) -> DatapackModel:
|
||||
push_error("DatapackLoader: Main Resource file not found at path: %s" % resource_path)
|
||||
return null
|
||||
|
||||
# Godot automatically detects the binary format and loads the resource.
|
||||
var datapack: DatapackModel = ResourceLoader.load(resource_path)
|
||||
|
||||
if not datapack:
|
||||
@ -42,14 +44,3 @@ func load_datapack(pack_name: String) -> DatapackModel:
|
||||
|
||||
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)
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
extends Node
|
||||
|
||||
func _enter_tree() -> void:
|
||||
var dp: DatapackModel = DatapackModel.new()
|
||||
var dpd: DatapackDependency = DatapackDependency.new()
|
||||
dpd.id = Guid.new_guid()
|
||||
dpd.name = "test"
|
||||
dpd.version = "1.0.0"
|
||||
dp.dependencies.append(dpd)
|
||||
|
||||
var json_string := JSON.stringify(dp.to_dict(), " ")
|
||||
print(json_string)
|
||||
Loading…
Reference in New Issue
Block a user