diff --git a/.godot/editor/filesystem_cache10 b/.godot/editor/filesystem_cache10 index d20a515..11182b7 100644 --- a/.godot/editor/filesystem_cache10 +++ b/.godot/editor/filesystem_cache10 @@ -1,5 +1,5 @@ 63f7b34db8d8cdea90c76aacccf841ec -::res://::1787270429 +::res://::1787270484 README.md::TextFile::-1::1787013015::0::1::::<><><>0<>0<><>:: ::res://assets/::1787270429 ::res://assets/avatars/::1787270429 @@ -20,7 +20,7 @@ main_menu.gd::GDScript::6526915941780468089::1787270429::0::1::::<>Control<><>0< avatar_catalog.gd::GDScript::5104367973301127782::1787270429::0::1::::AvatarCatalog<>RefCounted<><>0<>0<><>:: avatar_customization_ui.gd::GDScript::6000242829080071607::1787270429::0::1::::AvatarCustomizationUI<>Control<><>0<>0<><>:: avatar_loader.gd::GDScript::3645080687026000779::1787270429::0::1::::AvatarLoader<>Node<><>0<>0<><>:: -avatar_loadout.gd::GDScript::8780959424915557114::1787270429::0::1::::AvatarLoadout<>RefCounted<><>0<>0<><>:: +avatar_loadout.gd::GDScript::8780959424915557114::1787271124::0::1::::AvatarLoadout<>RefCounted<><>0<>0<><>:: avatar_slot_registry.gd::GDScript::8212023476238218904::1787270429::0::1::::AvatarSlotRegistry<>RefCounted<><>0<>0<><>:: avatar_system.gd::GDScript::150798117962859248::1787270429::0::1::::<>Node<><>0<>0<><>:: avatar_visualizer.gd::GDScript::6283745077729863623::1787270429::0::1::::AvatarVisualizer<>Object<><>0<>0<><>:: diff --git a/scripts/avatar/avatar_loadout.gd b/scripts/avatar/avatar_loadout.gd index f4acc87..ba996d9 100644 --- a/scripts/avatar/avatar_loadout.gd +++ b/scripts/avatar/avatar_loadout.gd @@ -32,7 +32,7 @@ var skin_tone: Color = Color.TRANSPARENT # --------------------------------------------------------------------------- ## Represents a single equipped item in a slot. -## Used by AvatarVisualizer to iterate over equipped items. +## Used by consumers to iterate over equipped items. class SlotItem: ## The slot this item belongs to. var slot_id: String @@ -60,13 +60,13 @@ func _init() -> void: static func from_json(json_string: String) -> AvatarLoadout: var loadout := AvatarLoadout.new() var data: Variant = JSON.parse_string(json_string) - if data is not Dictionary: + if not (data is Dictionary): loadout.validation_errors.append("Loadout JSON root is not an object") return loadout var root: Dictionary = data as Dictionary - # Parse base_body + # Parse base_body. if root.has("base_body"): var bb = root["base_body"] if typeof(bb) == TYPE_STRING: @@ -74,25 +74,29 @@ static func from_json(json_string: String) -> AvatarLoadout: else: loadout.validation_errors.append("base_body must be a string") - # Parse slots + # Parse slots. if root.has("slots"): var slots_data = root["slots"] if typeof(slots_data) == TYPE_DICTIONARY: - loadout.slots = slots_data as Dictionary + var parsed_slots: Dictionary = slots_data as Dictionary + for slot_id in parsed_slots: + if slot_id is String: + loadout.slots[slot_id] = parsed_slots[slot_id] else: loadout.validation_errors.append("slots must be an object") else: loadout.validation_errors.append("Missing 'slots' in loadout JSON") - # Parse skin_tone + # Parse skin_tone. if root.has("skin_tone"): var st = root["skin_tone"] if typeof(st) == TYPE_DICTIONARY: + var tone: Dictionary = st as Dictionary loadout.skin_tone = Color( - st.get("r", 0.0), - st.get("g", 0.0), - st.get("b", 0.0), - st.get("a", 1.0) + float(tone.get("r", 0.0)), + float(tone.get("g", 0.0)), + float(tone.get("b", 0.0)), + float(tone.get("a", 1.0)) ) return loadout @@ -107,11 +111,11 @@ func to_json() -> String: for slot_id in AvatarSlotRegistry.SLOT_IDS: if slot_id == "accessory": - data.slots[slot_id] = slots.get(slot_id, []) + data["slots"][slot_id] = slots.get(slot_id, []) else: - data.slots[slot_id] = slots.get(slot_id, null) + data["slots"][slot_id] = slots.get(slot_id, null) - # Include skin_tone if set + # Include skin_tone if set. if skin_tone != Color.TRANSPARENT: data["skin_tone"] = { "r": skin_tone.r, @@ -132,11 +136,11 @@ func to_json() -> String: func validate() -> bool: validation_errors.clear() - # Check base_body + # Check base_body. if base_body.is_empty(): validation_errors.append("base_body is required") - # Check each slot + # Check each known slot. for slot_id in AvatarSlotRegistry.SLOT_IDS: var slot_def = AvatarSlotRegistry.get_slot(slot_id) if slot_def == null: @@ -146,7 +150,7 @@ func validate() -> bool: var value = slots.get(slot_id) if slot_def.exclusive: - # Exclusive slots: must be null or a string + # Exclusive slots: must be null or a string. if value != null and typeof(value) != TYPE_STRING: validation_errors.append( "Slot '%s' must be null or a string, got %s" % [ @@ -154,32 +158,29 @@ func validate() -> bool: ] ) else: - # Non-exclusive (accessory): must be null or an array + # Non-exclusive (accessory): must be null or an array. if value != null and typeof(value) != TYPE_ARRAY: validation_errors.append( "Slot '%s' must be null or an array, got %s" % [ slot_id, typeof(value) ] ) - elif value is Array: - if slot_id == "accessory": - if value.size() > MAX_ACCESSORIES: - validation_errors.append( - "Accessory count (%d) exceeds maximum (%d)" % [ - value.size(), MAX_ACCESSORIES - ] - ) + elif value is Array: + if slot_id == "accessory" and (value as Array).size() > MAX_ACCESSORIES: + validation_errors.append( + "Accessory count (%d) exceeds maximum (%d)" % [ + (value as Array).size(), MAX_ACCESSORIES + ] + ) - # Check for duplicate accessories + # Check for duplicate accessories. if slots.has("accessory") and slots["accessory"] is Array: - var accessories = slots["accessory"] as Array + var accessories := slots["accessory"] as Array var seen := {} for item in accessories: if item is String: if seen.has(item): - validation_errors.append( - "Duplicate accessory: %s" % item - ) + validation_errors.append("Duplicate accessory: %s" % item) seen[item] = true return validation_errors.is_empty() @@ -231,20 +232,12 @@ func set_equipment(slot_id: String, asset_id: String) -> void: push_error("Slot '%s' is not exclusive" % slot_id) return - if slots.has(slot_id): - var current = slots[slot_id] - if current is Array: - # Convert array to single value - slots[slot_id] = asset_id if asset_id.is_empty() else null - else: - slots[slot_id] = asset_id if not asset_id.is_empty() else null - else: - slots[slot_id] = asset_id if not asset_id.is_empty() else null + slots[slot_id] = null if asset_id.is_empty() else asset_id ## Add an accessory to the accessory slot. func add_accessory(asset_id: String) -> bool: - var accessories = slots.get("accessory", []) as Array + var accessories := slots.get("accessory", []) as Array if accessories.size() >= MAX_ACCESSORIES: return false if asset_id.is_empty(): @@ -258,7 +251,7 @@ func add_accessory(asset_id: String) -> bool: ## Remove an accessory from the accessory slot. func remove_accessory(asset_id: String) -> bool: - var accessories = slots.get("accessory", []) as Array + var accessories := slots.get("accessory", []) as Array if accessories.has(asset_id): accessories.erase(asset_id) slots["accessory"] = accessories @@ -277,12 +270,12 @@ func get_equipped_items() -> Array[SlotItem]: continue if typeof(value) == TYPE_STRING: - var asset_id = value as String + var asset_id := value as String if not asset_id.is_empty(): items.append(SlotItem.new(slot_id, asset_id)) elif typeof(value) == TYPE_ARRAY: for item_id in value: - if item_id is String and not item_id.is_empty(): + if item_id is String and not (item_id as String).is_empty(): items.append(SlotItem.new(slot_id, item_id)) return items @@ -302,12 +295,12 @@ func compute_hash() -> int: hasher.update(slot_id.to_utf8_buffer()) var value = slots.get(slot_id) if value is String: - hasher.update(value.to_utf8_buffer()) + hasher.update((value as String).to_utf8_buffer()) elif value is Array: var arr_str = JSON.stringify(value) hasher.update(arr_str.to_utf8_buffer()) else: hasher.update("null".to_utf8_buffer()) var digest = hasher.finish() - # Return first 4 bytes as int for compact storage + # Return first 4 bytes as int for compact storage. return digest[0] | (digest[1] << 8) | (digest[2] << 16) | (digest[3] << 24)