straywild/tests/character_customization_expansion_validation.gd

278 lines
9.8 KiB
GDScript

extends SceneTree
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_validate_catalog_and_legacy_names()
_validate_version_12_migration()
_validate_appearance_profile_migration_and_strictness()
_validate_signatures_cover_independent_accessories()
await _validate_presenter_selection()
print("Character customization expansion validation: PASS")
quit()
func _validate_catalog_and_legacy_names() -> void:
var defaults := CharacterCustomizationCatalog.default_snapshot()
assert(CharacterCustomizationCatalog.validate_snapshot(defaults))
assert(defaults.size() == CharacterCustomizationCatalog.SNAPSHOT_IDS.size())
assert(CharacterCustomizationCatalog.SNAPSHOT_IDS.size() == 23)
assert(CharacterCustomizationCatalog.CATEGORY_IDS.count("special") == 1)
assert("special" not in CharacterCustomizationCatalog.SNAPSHOT_IDS)
assert(CharacterCustomizationCatalog.SPECIAL_SLOT_IDS == PackedStringArray([
"special_back",
"special_beak",
"special_head",
"special_horns",
"special_tusks",
"special_wings",
]))
var expected_counts := {
"species": 21,
"arms": 3,
"ears": 21,
"tail": 17,
"special_back": 1,
"special_beak": 2,
"special_head": 1,
"special_horns": 2,
"special_tusks": 2,
"special_wings": 3,
}
var all_mesh_names: Array[String] = []
for category_id: String in expected_counts:
var options := CharacterCustomizationCatalog.options_for(category_id)
var option_ids: Array[String] = []
var mesh_names: Array[String] = []
for option: Dictionary in options:
var option_id := str(option.get("id", ""))
assert(not option_id.is_empty())
assert(option_id not in option_ids)
option_ids.append(option_id)
var mesh_name := CharacterCustomizationCatalog.model_mesh_name(
category_id,
option_id,
)
if option_id == "none":
assert(mesh_name.is_empty())
continue
assert(not mesh_name.is_empty())
assert(mesh_name not in mesh_names)
assert(mesh_name not in all_mesh_names)
mesh_names.append(mesh_name)
all_mesh_names.append(mesh_name)
assert(mesh_names.size() == int(expected_counts[category_id]))
assert(
CharacterCustomizationCatalog.model_mesh_names(category_id).size()
== int(expected_counts[category_id])
)
assert(
CharacterCustomizationCatalog.model_mesh_names("special").size()
== 11
)
assert(
CharacterCustomizationCatalog.model_mesh_name("arms", "body_arms")
== "arms_mammal"
)
assert(
CharacterCustomizationCatalog.model_mesh_name("ears", "pointy_long")
== "ears_long"
)
assert(
CharacterCustomizationCatalog.model_mesh_name("ears", "pointy_short")
== "ears_cat"
)
assert(
CharacterCustomizationCatalog.model_mesh_name("ears", "pointy_wide")
== "ears_fox"
)
var legacy := defaults.duplicate(true)
legacy.erase("arms")
for special_slot_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
legacy.erase(special_slot_id)
legacy["ears"] = "pointy_wide"
var sanitized := CharacterCustomizationCatalog.sanitized_snapshot(legacy)
assert(CharacterCustomizationCatalog.validate_snapshot(sanitized))
assert(sanitized["arms"] == "mammal")
assert(sanitized["ears"] == "fox")
for special_slot_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
assert(sanitized[special_slot_id] == "none")
func _validate_version_12_migration() -> void:
var legacy_appearance := CharacterCustomizationCatalog.default_snapshot()
legacy_appearance.erase("arms")
for special_slot_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
legacy_appearance.erase(special_slot_id)
legacy_appearance["ears"] = "pointy_short"
var manager := PlayerSaveManager.new()
root.add_child(manager)
var migrated: Dictionary = manager.call(
"_migrate_save",
{
"save_version": 12,
"character": {
"appearance": legacy_appearance,
},
},
12,
)
assert(int(migrated.get("save_version", -1)) == 13)
var appearance: Dictionary = migrated["character"]["appearance"]
assert(CharacterCustomizationCatalog.validate_snapshot(appearance))
assert(appearance["arms"] == "mammal")
assert(appearance["ears"] == "cat")
for special_slot_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
assert(appearance[special_slot_id] == "none")
manager.queue_free()
func _validate_appearance_profile_migration_and_strictness() -> void:
var legacy := CharacterCustomizationCatalog.default_snapshot()
legacy.erase("arms")
for special_slot_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
legacy.erase(special_slot_id)
legacy["ears"] = "pointy_wide"
var legacy_path := "user://appearance-v1-%d.json" % Time.get_ticks_usec()
var legacy_file := FileAccess.open(legacy_path, FileAccess.WRITE)
assert(legacy_file != null)
legacy_file.store_string(JSON.stringify({
"format_version": 1,
"appearance": legacy,
}))
legacy_file.close()
var legacy_store := PlayerAppearanceStore.new()
root.add_child(legacy_store)
legacy_store.configure_storage(legacy_path, null)
assert(legacy_store.load_preferences())
var migrated := legacy_store.get_snapshot()
assert(CharacterCustomizationCatalog.validate_snapshot(migrated))
assert(migrated["arms"] == "mammal")
assert(migrated["ears"] == "fox")
legacy_store.queue_free()
DirAccess.remove_absolute(ProjectSettings.globalize_path(legacy_path))
var malformed := CharacterCustomizationCatalog.default_snapshot()
malformed["species"] = "definitely_not_a_head"
var current_path := "user://appearance-v2-%d.json" % Time.get_ticks_usec()
var current_file := FileAccess.open(current_path, FileAccess.WRITE)
assert(current_file != null)
current_file.store_string(JSON.stringify({
"format_version": PlayerAppearanceStore.FORMAT_VERSION,
"appearance": malformed,
}))
current_file.close()
var current_store := PlayerAppearanceStore.new()
root.add_child(current_store)
current_store.configure_storage(current_path, null)
assert(not current_store.load_preferences())
assert(not current_store.restore_from_save_data(malformed, false))
current_store.queue_free()
DirAccess.remove_absolute(ProjectSettings.globalize_path(current_path))
func _validate_signatures_cover_independent_accessories() -> void:
var baseline := CharacterCustomizationCatalog.default_snapshot()
var previous_signature := NetworkProfileProtocol.signature_fields({
"appearance": baseline,
})
var selections := {
"special_back": "shell",
"special_beak": "bird",
"special_head": "fin",
"special_horns": "goat",
"special_tusks": "boar",
"special_wings": "bat",
}
for category_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
var changed := baseline.duplicate(true)
changed[category_id] = selections[category_id]
assert(CharacterCustomizationCatalog.validate_snapshot(changed))
var changed_signature := NetworkProfileProtocol.signature_fields({
"appearance": changed,
})
assert(changed_signature != previous_signature)
previous_signature = changed_signature
func _validate_presenter_selection() -> void:
var visuals := PlayerVisualPresenter.instantiate_visuals()
root.add_child(visuals)
var skeleton := visuals.find_child("Skeleton3D", true, false) as Skeleton3D
assert(skeleton != null)
var snapshot := CharacterCustomizationCatalog.default_snapshot()
PlayerVisualPresenter.apply_appearance(visuals, snapshot)
_assert_exact_selection(skeleton, "species", "head_round")
_assert_exact_selection(skeleton, "arms", "arms_mammal")
_assert_exact_selection(skeleton, "ears", "")
_assert_exact_selection(skeleton, "tail", "")
for special_slot_id: String in CharacterCustomizationCatalog.SPECIAL_SLOT_IDS:
_assert_exact_selection(skeleton, special_slot_id, "")
for species_option: Dictionary in CharacterCustomizationCatalog.options_for(
"species"
):
var species_id := str(species_option.get("id", ""))
snapshot["species"] = species_id
PlayerVisualPresenter.apply_appearance(visuals, snapshot)
var selected_head := CharacterCustomizationCatalog.model_mesh_name(
"species",
species_id,
)
_assert_exact_selection(skeleton, "species", selected_head)
for feature_name: String in ["eyes", "nose", "mouth"]:
var feature := skeleton.get_node_or_null(
"%s_%s" % [selected_head, feature_name]
) as MeshInstance3D
assert(feature != null and feature.visible)
var feature_material := feature.material_override as StandardMaterial3D
assert(feature_material != null)
assert(feature_material.albedo_texture != null)
snapshot["arms"] = "avian"
snapshot["ears"] = "owl"
snapshot["tail"] = "donkey"
snapshot["special_back"] = "shell"
snapshot["special_beak"] = "bird"
snapshot["special_head"] = "fin"
snapshot["special_horns"] = "goat"
snapshot["special_tusks"] = "elephant"
snapshot["special_wings"] = "moth"
assert(CharacterCustomizationCatalog.validate_snapshot(snapshot))
PlayerVisualPresenter.apply_appearance(visuals, snapshot)
_assert_exact_selection(skeleton, "arms", "arms_avian")
_assert_exact_selection(skeleton, "ears", "ears_owl")
_assert_exact_selection(skeleton, "tail", "tail_donkey")
_assert_exact_selection(skeleton, "special_back", "back_shell")
_assert_exact_selection(skeleton, "special_beak", "beak_bird")
_assert_exact_selection(skeleton, "special_head", "head_fin")
_assert_exact_selection(skeleton, "special_horns", "horns_goat")
_assert_exact_selection(skeleton, "special_tusks", "tusks_elephant")
_assert_exact_selection(skeleton, "special_wings", "wings_moth")
var wings := skeleton.get_node("wings_moth") as MeshInstance3D
var cached_material := wings.material_override
assert(cached_material != null)
PlayerVisualPresenter.apply_appearance(visuals, snapshot)
assert(wings.material_override == cached_material)
visuals.queue_free()
await process_frame
func _assert_exact_selection(
skeleton: Skeleton3D,
category_id: String,
expected_mesh_name: String,
) -> void:
for mesh_name: String in CharacterCustomizationCatalog.model_mesh_names(
category_id
):
var mesh := skeleton.get_node_or_null(mesh_name) as Node3D
assert(mesh != null)
assert(mesh.visible == (mesh_name == expected_mesh_name))