class_name CharacterData ## Loads one character folder (see characters/_spec/CHARACTER_SPEC.md). ## The roster is whatever folders exist under characters/ — adding a ## fighter is dropping a folder in, no code changes. var id := "" var display_name := "" var health := 1000 var walk_speed := 3.0 var back_speed := 2.5 var jump_velocity := -13.5 var color := Color.WHITE var sprite_scale := 1.0 # draw scale; also scales all box coords var wardrobe_states: Array = [] # ordered, e.g. ["base", "torn"] var tear_below_hp := 0.0 # fraction; reaching it forces the last state var normals := {} # "P" -> move name, "6P" -> forward+P, "K" -> ... var command_moves: Array = [] # [{motion:[2,3,6], button:"P", move:"rush_palm"}] var finisher := {} # {motion, button, move} — offered while foe is dizzy var moves := {} # name -> MoveData var portrait: Texture2D = null static func load_dir(dir_path: String) -> CharacterData: var c := CharacterData.new() var manifest_text := FileAccess.get_file_as_string(dir_path + "/manifest.json") if manifest_text.is_empty(): push_error("CharacterData: missing manifest.json in " + dir_path) return c var d = JSON.parse_string(manifest_text) # editor overrides (user://overrides/.json) merge over the manifest var ov_text := FileAccess.get_file_as_string( "user://overrides/%s.json" % dir_path.get_file()) if not ov_text.is_empty(): var ov = JSON.parse_string(ov_text) if ov is Dictionary: for k in ov: d[k] = ov[k] c.id = d.get("id", dir_path.get_file()) c.display_name = d.get("name", c.id.to_upper()) c.health = int(d.get("health", 1000)) c.walk_speed = float(d.get("walk_speed", 3.0)) c.back_speed = float(d.get("back_speed", 2.5)) c.jump_velocity = float(d.get("jump_velocity", -13.5)) c.color = Color(d.get("color", "#ffffff")) c.sprite_scale = float(d.get("sprite_scale", 1.0)) var w = d.get("wardrobe", null) if w != null: c.wardrobe_states = w.get("states", []) c.tear_below_hp = float(w.get("tear_below_hp", 0.0)) c.normals = d.get("normals", {}) c.command_moves = d.get("command_moves", []) c.finisher = d.get("finisher", {}) for move_dir in MoveData.list_dirs(dir_path + "/moves"): c.moves[move_dir] = MoveData.load_dir(dir_path + "/moves/" + move_dir) c.portrait = MoveData.load_texture_any(dir_path + "/portrait.png") return c ## Roster = built-in characters plus user://characters drop-ins (editor ## output / mods). A user folder with the same id overrides the built-in. static func scan_roster(root := "res://characters") -> Array: var found := {} var dirs := Array(MoveData.list_dirs(root)) for d in dirs: if d.begins_with("_") or d.begins_with("."): continue found[d] = root + "/" + d for d in DirAccess.get_directories_at("user://characters"): if not (d.begins_with("_") or d.begins_with(".")): found[d] = "user://characters/" + d var ids := found.keys() ids.sort() var out: Array = [] for id in ids: out.append(found[id]) return out