- Title: PLAY / EDITOR entry - Editor (in-app): edit fighter name/stats/colour -> user://overrides, import character folders -> user://characters, add stage backgrounds -> user://stages, open content folder - loaders work in editor, exported .pck, and user:// drop-ins (ResourceLoader remap-stripping + byte-buffer fallbacks) - stage backgrounds render behind fights when present - export_presets: mac (universal, ad-hoc sign), win x86_64, linux x86_64 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
79 lines
2.9 KiB
GDScript
79 lines
2.9 KiB
GDScript
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/<id>.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
|