Phase 14 - Organized directories.
This commit is contained in:
+3
-3
@@ -4,9 +4,9 @@ extends Node
|
||||
## StickmanRig remains the public facade; this node only calls down into
|
||||
## locomotion and signals requests back to the facade.
|
||||
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const SPEECH_BUBBLE_SCRIPT := preload("res://scripts/stickman_speech_bubble.gd")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech_payload.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const SPEECH_BUBBLE_SCRIPT := preload("res://scripts/speech/stickman_speech_bubble.gd")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech/speech_payload.gd")
|
||||
|
||||
enum RunnerState { IDLE, EXECUTING }
|
||||
enum ActionPhase { NONE, WALKING, SPEAKING, WAITING, RAGDOLLING, RECOVERING, PROP_INTERACTION }
|
||||
@@ -3,7 +3,7 @@ extends RefCounted
|
||||
## StickmanFactory - Runtime factory for spawning rig instances from .stk files
|
||||
## (Phase 9).
|
||||
##
|
||||
## Loads a versioned .stk JSON dictionary and instantiates scenes/stickman_rig.tscn,
|
||||
## Loads a versioned .stk JSON dictionary and instantiates scenes/characters/stickman/stickman_rig.tscn,
|
||||
## adapting it via StkRigAdapter. Consumed by the standalone test harness, not
|
||||
## referenced by the editor.
|
||||
|
||||
@@ -11,13 +11,15 @@ extends RefCounted
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
const RIG_SCENE := preload("res://scenes/stickman_rig.tscn")
|
||||
const RIG_SCENE := preload("res://scenes/characters/stickman/stickman_rig.tscn")
|
||||
const CONTENT_PATHS := preload("res://scripts/shared/content_paths.gd")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
static func load_stk(path: String) -> Dictionary:
|
||||
path = CONTENT_PATHS.canonicalize(path)
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
push_warning("StickmanFactory: failed to open '%s' (error %d)." % [path, FileAccess.get_open_error()])
|
||||
@@ -1,9 +1,9 @@
|
||||
class_name StickmanLibrary
|
||||
extends RefCounted
|
||||
|
||||
const STICKMAN_FACTORY := preload("res://scripts/stickman_factory.gd")
|
||||
const STICKMAN_FACTORY := preload("res://scripts/characters/stickman/stickman_factory.gd")
|
||||
|
||||
const STICKMEN_DIR := "res://stickmen"
|
||||
const STICKMEN_DIR := "res://content/stickmen"
|
||||
|
||||
var entries: Array[Dictionary] = []
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ class_name StickmanPropInteractor
|
||||
extends Node
|
||||
## Owns one-stickman/one-hand prop interaction state during Sandbox PLAY.
|
||||
|
||||
const ANCHOR_DATA := preload("res://scripts/prop_anchor_data.gd")
|
||||
const ANCHOR_DATA := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
|
||||
enum Hand { LEFT, RIGHT }
|
||||
enum Phase { IDLE, APPROACHING, REACHING, PICKUP_RECOVERING, HELD, THROWING }
|
||||
@@ -1,9 +1,9 @@
|
||||
class_name StickmanRig
|
||||
extends Node2D
|
||||
## StickmanRig - Runtime owner of facing direction and per-joint bone bend for
|
||||
## scenes/stickman_rig.tscn (Phase 9 / Task 4).
|
||||
## scenes/characters/stickman/stickman_rig.tscn (Phase 9 / Task 4).
|
||||
##
|
||||
## Attached to the `Master` root node of scenes/stickman_rig.tscn. Owns the facing
|
||||
## Attached to the `Master` root node of scenes/characters/stickman/stickman_rig.tscn. Owns the facing
|
||||
## preset, the four per-joint TwoBoneIK "Flip Bend Direction" flags, and the
|
||||
## Body/* draw order. Non-@tool: node resolution, flag writes and z-order
|
||||
## reordering run only at runtime (_ready + setters on a live instance).
|
||||
@@ -2,7 +2,7 @@ class_name StkRigAdapter
|
||||
extends RefCounted
|
||||
## StkRigAdapter - Standalone runtime adapter (Phase 8).
|
||||
##
|
||||
## Fits an instantiated scenes/stickman_rig.tscn to a loaded .stk dictionary: re-fits
|
||||
## Fits an instantiated scenes/characters/stickman/stickman_rig.tscn to a loaded .stk dictionary: re-fits
|
||||
## the skeleton bone lengths, recalibrates the IK targets, and mounts the
|
||||
## .stk vector shapes onto the rig's Body/ visual nodes in the rig's "hanging"
|
||||
## convention (joint end at the local origin, far end along +Y). The
|
||||
@@ -4,10 +4,10 @@ extends PanelContainer
|
||||
|
||||
enum PreviewHand { LEFT, RIGHT }
|
||||
enum PreviewMotion { FRONT_POSE, WALK_LEFT, WALK_RIGHT }
|
||||
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
|
||||
const StickmanFactoryScript := preload("res://scripts/stickman_factory.gd")
|
||||
const StkRigAdapterScript := preload("res://scripts/stk_rig_adapter.gd")
|
||||
const RigScene := preload("res://scenes/stickman_rig.tscn")
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const StickmanFactoryScript := preload("res://scripts/characters/stickman/stickman_factory.gd")
|
||||
const StkRigAdapterScript := preload("res://scripts/characters/stickman/stk_rig_adapter.gd")
|
||||
const RigScene := preload("res://scenes/characters/stickman/stickman_rig.tscn")
|
||||
const RIG_CENTER := Vector2(340.0, 575.0)
|
||||
const TARGET_RADIUS := 14.0
|
||||
|
||||
@@ -135,7 +135,7 @@ func _build_private_world() -> void:
|
||||
_rig = RigScene.instantiate() as StickmanRig
|
||||
_rig.set_physics_process(false)
|
||||
_rig_root.add_child(_rig)
|
||||
var data: Dictionary = StickmanFactoryScript.load_stk("res://stickmen/test.stk")
|
||||
var data: Dictionary = StickmanFactoryScript.load_stk("res://content/stickmen/test.stk")
|
||||
if not data.is_empty():
|
||||
StkRigAdapterScript.apply(data, _rig)
|
||||
_prop = Node2D.new()
|
||||
@@ -70,11 +70,11 @@ const ZOOM_STEP: float = 1.10
|
||||
const GRID_COLOR := Color(1.0, 1.0, 1.0, 0.15)
|
||||
const ORIGIN_MARKER_DEFAULT_COLOR := Color(0.6, 0.6, 0.6, 0.7)
|
||||
|
||||
const TerrainUtilsScript := preload("res://scripts/terrain_utils.gd")
|
||||
const PropFileScript := preload("res://scripts/prop_file.gd")
|
||||
const PropAnchorOverlayScript := preload("res://scripts/prop_anchor_overlay.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
|
||||
const WholeStickmanPreviewScript := preload("res://scripts/whole_stickman_preview.gd")
|
||||
const TerrainUtilsScript := preload("res://scripts/terrain/terrain_utils.gd")
|
||||
const PropFileScript := preload("res://scripts/editors/prop/prop_file.gd")
|
||||
const PropAnchorOverlayScript := preload("res://scripts/props/prop_anchor_overlay.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const WholeStickmanPreviewScript := preload("res://scripts/editors/stickman/whole_stickman_preview.gd")
|
||||
|
||||
const SELECTION_CORE_COLOR := Color.WHITE
|
||||
const SELECTION_HALO_COLOR := Color(0.0, 0.0, 0.0, 0.85)
|
||||
@@ -10,14 +10,15 @@ extends Control
|
||||
## (never mutates global GUI settings), the shape clipboard, and the
|
||||
## save-side thumbnail pipeline (PropThumbnail + ThumbnailCache).
|
||||
|
||||
const PropFileScript := preload("res://scripts/prop_file.gd")
|
||||
const PropThemeScript := preload("res://scripts/prop_theme.gd")
|
||||
const PropBlockScript := preload("res://scripts/prop_block.gd")
|
||||
const PropCanvasScript := preload("res://scripts/prop_canvas.gd")
|
||||
const PropThumbnailScript := preload("res://scripts/thumbnails/prop_thumbnail.gd")
|
||||
const ThumbnailCacheScript := preload("res://scripts/thumbnails/thumbnail_cache.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
|
||||
const PropAttachPreviewScene := preload("res://scenes/prop_attach_preview.tscn")
|
||||
const PropFileScript := preload("res://scripts/editors/prop/prop_file.gd")
|
||||
const PropThemeScript := preload("res://scripts/editors/prop/prop_theme.gd")
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
const PropCanvasScript := preload("res://scripts/editors/prop/prop_canvas.gd")
|
||||
const PropThumbnailScript := preload("res://scripts/shared/thumbnails/prop_thumbnail.gd")
|
||||
const ThumbnailCacheScript := preload("res://scripts/shared/thumbnails/thumbnail_cache.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const PropAttachPreviewScene := preload("res://scenes/editors/prop/prop_attach_preview.tscn")
|
||||
const ContentPathsScript := preload("res://scripts/shared/content_paths.gd")
|
||||
|
||||
const PRESET_LABELS: PackedStringArray = ["None", "Wood", "Rubber", "Cardboard", "Metal"]
|
||||
const PRIMITIVE_IDS: PackedStringArray = ["box", "plank", "triangle", "line", "circle"]
|
||||
@@ -64,11 +65,13 @@ static func build_window_title(prop_name: String, file_path: String, dirty: bool
|
||||
|
||||
## Most-recent-first dedupe + front-insert + trim (headless-testable).
|
||||
static func update_recent_list(list: Array, path: String, max_count: int = RECENT_MAX) -> Array:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
var result: Array = []
|
||||
result.append(path)
|
||||
for entry: Variant in list:
|
||||
if str(entry) != path:
|
||||
result.append(str(entry))
|
||||
var canonical_entry: String = ContentPathsScript.canonicalize(str(entry))
|
||||
if canonical_entry != path and not result.has(canonical_entry):
|
||||
result.append(canonical_entry)
|
||||
if result.size() > max_count:
|
||||
result.resize(max_count)
|
||||
return result
|
||||
@@ -526,8 +529,9 @@ func _update_recent_menu() -> void:
|
||||
recent.clear()
|
||||
var existing: Array[String] = []
|
||||
for path: String in _recent_props:
|
||||
if FileAccess.file_exists(path):
|
||||
existing.append(path)
|
||||
var canonical_path: String = ContentPathsScript.canonicalize(path)
|
||||
if FileAccess.file_exists(canonical_path) and not existing.has(canonical_path):
|
||||
existing.append(canonical_path)
|
||||
if existing.size() != _recent_props.size():
|
||||
_recent_props = existing
|
||||
_save_settings()
|
||||
@@ -542,7 +546,7 @@ func _update_recent_menu() -> void:
|
||||
func _on_recent_prop_selected(index: int) -> void:
|
||||
if index < 0 or index >= _recent_props.size():
|
||||
return
|
||||
var path := _recent_props[index]
|
||||
var path: String = ContentPathsScript.canonicalize(_recent_props[index])
|
||||
if not FileAccess.file_exists(path):
|
||||
_recent_props.remove_at(index)
|
||||
_save_settings()
|
||||
@@ -552,6 +556,7 @@ func _on_recent_prop_selected(index: int) -> void:
|
||||
|
||||
|
||||
func _push_recent(path: String) -> void:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
var updated: Array = update_recent_list(_recent_props, path, RECENT_MAX)
|
||||
_recent_props.clear()
|
||||
for entry: Variant in updated:
|
||||
@@ -574,9 +579,16 @@ func _load_settings() -> void:
|
||||
_show_stickman_guide = bool((parsed as Dictionary).get("show_stickman_guide", false))
|
||||
var raw_list: Variant = (parsed as Dictionary).get("recent_props", [])
|
||||
if raw_list is Array:
|
||||
var migrated: bool = false
|
||||
for entry: Variant in raw_list as Array:
|
||||
if entry is String and not (entry as String).is_empty():
|
||||
_recent_props.append(entry as String)
|
||||
var old_path: String = entry as String
|
||||
var path: String = ContentPathsScript.canonicalize(old_path)
|
||||
migrated = migrated or path != old_path
|
||||
if not _recent_props.has(path):
|
||||
_recent_props.append(path)
|
||||
if migrated:
|
||||
_save_settings()
|
||||
|
||||
|
||||
func _save_settings() -> void:
|
||||
@@ -589,6 +601,9 @@ func _save_settings() -> void:
|
||||
|
||||
|
||||
func _setup_dialogs() -> void:
|
||||
var content_dir: String = ProjectSettings.globalize_path(ContentPathsScript.PROPS_DIR)
|
||||
_save_dialog.current_dir = content_dir
|
||||
_load_dialog.current_dir = content_dir
|
||||
_save_dialog.file_selected.connect(_on_save_file_selected)
|
||||
_load_dialog.file_selected.connect(_on_load_file_selected)
|
||||
_build_exit_dialog()
|
||||
@@ -1399,7 +1414,7 @@ func _write_autosave() -> void:
|
||||
var data: Dictionary = _build_json_data()
|
||||
var meta: Dictionary = data.get("metadata", {})
|
||||
meta["autosave"] = true
|
||||
meta["source_path"] = _current_path
|
||||
meta["source_path"] = ContentPathsScript.canonicalize(_current_path)
|
||||
meta["modified_unix"] = Time.get_unix_time_from_system()
|
||||
data["metadata"] = meta
|
||||
PropFileScript.save_safe(AUTOSAVE_PATH, data)
|
||||
@@ -1423,7 +1438,7 @@ func _check_autosave_recovery() -> void:
|
||||
if not bool(metadata.get("autosave", false)):
|
||||
_clear_autosave()
|
||||
return
|
||||
var source_path := String(metadata.get("source_path", ""))
|
||||
var source_path: String = ContentPathsScript.canonicalize(String(metadata.get("source_path", "")))
|
||||
# Stale if the saved source exists and is newer-or-equal than the autosave.
|
||||
if not source_path.is_empty() and FileAccess.file_exists(source_path) \
|
||||
and int(metadata.get("modified_unix", 0)) <= int(FileAccess.get_modified_time(source_path)):
|
||||
@@ -1485,6 +1500,7 @@ func _apply_json_data(json: Variant, is_recovery: bool = false) -> String:
|
||||
|
||||
|
||||
func _save_to(path: String, with_thumbnail: bool = true) -> bool:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
var ok: bool = PropFileScript.save(path, _build_json_data())
|
||||
if not ok:
|
||||
_show_error("Could not save the prop to '%s'." % path)
|
||||
@@ -1546,12 +1562,14 @@ func _cancel_pending_save_request() -> void:
|
||||
_pending_new = false
|
||||
|
||||
func _on_save_file_selected(path: String) -> void:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
if path.get_extension().to_lower() != "prop":
|
||||
path = path.get_basename() + ".prop"
|
||||
_begin_save_request(path, not (_pending_quit or _pending_new))
|
||||
|
||||
|
||||
func _load_from(path: String) -> void:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
var raw: Dictionary = PropFileScript.load(path)
|
||||
if raw.is_empty():
|
||||
_show_error("Could not load '%s'. The file is missing or not valid JSON." % path)
|
||||
@@ -7,13 +7,14 @@ extends RefCounted
|
||||
## the stack into the single-shape payload PropUtils.spawn_prop() already
|
||||
## consumes: frontmost closed polygon -> else circle -> else first shape.
|
||||
|
||||
const PropBlockScript := preload("res://scripts/prop_block.gd")
|
||||
const TerrainUtilsScript := preload("res://scripts/terrain_utils.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
const TerrainUtilsScript := preload("res://scripts/terrain/terrain_utils.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const ContentPathsScript := preload("res://scripts/shared/content_paths.gd")
|
||||
|
||||
const FILE_VERSION: String = "1.0"
|
||||
const FILE_EXT: String = ".prop"
|
||||
const DEFAULT_DIR: String = "res://props"
|
||||
const DEFAULT_DIR: String = "res://content/props"
|
||||
const SUPPORTED_VERSIONS: Array[String] = ["1.0"]
|
||||
# index == PropBlock.MaterialPreset
|
||||
const PRESET_STRINGS: PackedStringArray = ["none", "wood", "rubber", "cardboard", "metal"]
|
||||
@@ -160,6 +161,7 @@ static func flatten_to_payload(shapes: Array[Dictionary]) -> Dictionary:
|
||||
|
||||
## Write `data` (a to_file_dict result) as pretty-printed JSON to `path`.
|
||||
static func save(path: String, data: Dictionary) -> bool:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
push_warning("PropFile: could not open '%s' for writing." % path)
|
||||
@@ -173,6 +175,7 @@ static func save(path: String, data: Dictionary) -> bool:
|
||||
## swap the previous file to .bak, rename the temp into place, then remove the
|
||||
## backup. Used by the editor autosave so a crash mid-write cannot corrupt it.
|
||||
static func save_safe(path: String, data: Dictionary) -> bool:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
var tmp := path + ".tmp"
|
||||
var bak := path + ".bak"
|
||||
var file := FileAccess.open(tmp, FileAccess.WRITE)
|
||||
@@ -200,6 +203,7 @@ static func save_safe(path: String, data: Dictionary) -> bool:
|
||||
## on read/parse failure (with a push_warning). Schema validation (version,
|
||||
## shapes) happens in from_file_dict.
|
||||
static func load(path: String) -> Dictionary:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
if not FileAccess.file_exists(path):
|
||||
push_warning("PropFile: '%s' does not exist." % path)
|
||||
return {}
|
||||
@@ -9,7 +9,7 @@ extends RefCounted
|
||||
## NOT themeable; PropBlock's static preset tables remain the single source of
|
||||
## truth.
|
||||
|
||||
const PropBlockScript := preload("res://scripts/prop_block.gd")
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
|
||||
const SCHEMA_VERSION: String = "1.0"
|
||||
const DEFAULT_PATH: String = "res://prop_theme.json"
|
||||
@@ -8,7 +8,7 @@ extends PopupPanel
|
||||
## captured on the stage: the editor emits `target_requested()` and the stage
|
||||
## hides the editor, captures a click, then calls `set_walk_target()`.
|
||||
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
|
||||
signal committed(action: Dictionary)
|
||||
signal cancelled()
|
||||
@@ -8,9 +8,9 @@ extends PopupPanel
|
||||
## emits `queue_changed` and redraws the director overlay. Editing and adding
|
||||
## delegate to the stage via signals so this panel stays decoupled.
|
||||
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const STICKMAN_RIG := preload("res://scripts/stickman_rig.gd")
|
||||
const ACTION_RUNNER := preload("res://scripts/stickman_action_runner.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const STICKMAN_RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
|
||||
const ACTION_RUNNER := preload("res://scripts/characters/stickman/stickman_action_runner.gd")
|
||||
|
||||
signal edit_requested(index: int)
|
||||
signal delete_requested(index: int)
|
||||
@@ -11,8 +11,8 @@ extends PopupPanel
|
||||
## signals; the stage hides this popup, captures a click, then calls
|
||||
## `set_trigger_target()` / `set_action()` and re-pops it.
|
||||
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const TRIGGER_REGISTRY := preload("res://scripts/trigger_registry.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const TRIGGER_REGISTRY := preload("res://scripts/stage/trigger_registry.gd")
|
||||
|
||||
signal committed(rule: Dictionary)
|
||||
signal cancelled()
|
||||
@@ -8,8 +8,8 @@ extends PopupPanel
|
||||
## order of the *displayed* rules' ids, which the stage maps back onto its
|
||||
## full `_event_rules` array (preserving un-filtered rules' positions).
|
||||
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const TRIGGER_REGISTRY := preload("res://scripts/trigger_registry.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const TRIGGER_REGISTRY := preload("res://scripts/stage/trigger_registry.gd")
|
||||
|
||||
signal edit_requested(rule_id: int)
|
||||
signal delete_requested(rule_id: int)
|
||||
@@ -7,43 +7,44 @@ extends Node2D
|
||||
## hit-testing; objects are dragged directly (no move handle) and rotated via a
|
||||
## ring handle. A placement ghost previews the object under the cursor, and an
|
||||
## optional snap-to-grid + grid overlay ease placement. NOT wired into the editor
|
||||
## - run standalone via F6 on res://scenes/sandbox_stage.tscn.
|
||||
## - run standalone via F6 on res://scenes/stage/sandbox_stage.tscn.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Preloaded helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
|
||||
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
|
||||
const STAGE_GIZMOS := preload("res://scripts/stage_gizmos.gd")
|
||||
const STAGE_GRID := preload("res://scripts/stage_grid.gd")
|
||||
const STICKMAN_RIG := preload("res://scripts/stickman_rig.gd")
|
||||
const STAGE_DIRECTOR_VISUALS := preload("res://scripts/stage_director_visuals.gd")
|
||||
const STAGE_PLACEMENT_OVERLAY := preload("res://scripts/stage_placement_overlay.gd")
|
||||
const ASSET_SELECTOR := preload("res://scenes/asset_selector.tscn")
|
||||
const STICKMAN_LIBRARY := preload("res://scripts/stickman_library.gd")
|
||||
const PROP_LIBRARY := preload("res://scripts/prop_library.gd")
|
||||
const THUMBNAIL_CACHE := preload("res://scripts/thumbnails/thumbnail_cache.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/sandbox_theme.gd")
|
||||
const ASSET_CARD := preload("res://scripts/ui/asset_card.gd")
|
||||
const STICKMAN_THUMBNAIL := preload("res://scripts/thumbnails/stickman_thumbnail.gd")
|
||||
const PROP_THUMBNAIL := preload("res://scripts/thumbnails/prop_thumbnail.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
|
||||
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
||||
const STAGE_GIZMOS := preload("res://scripts/stage/stage_gizmos.gd")
|
||||
const STAGE_GRID := preload("res://scripts/stage/stage_grid.gd")
|
||||
const STICKMAN_RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
|
||||
const STAGE_DIRECTOR_VISUALS := preload("res://scripts/stage/stage_director_visuals.gd")
|
||||
const STAGE_PLACEMENT_OVERLAY := preload("res://scripts/stage/stage_placement_overlay.gd")
|
||||
const ASSET_SELECTOR := preload("res://scenes/stage/asset_selector.tscn")
|
||||
const STICKMAN_LIBRARY := preload("res://scripts/characters/stickman/stickman_library.gd")
|
||||
const PROP_LIBRARY := preload("res://scripts/props/prop_library.gd")
|
||||
const THUMBNAIL_CACHE := preload("res://scripts/shared/thumbnails/thumbnail_cache.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/stage/sandbox_theme.gd")
|
||||
const ASSET_CARD := preload("res://scripts/shared/ui/asset_card.gd")
|
||||
const STICKMAN_THUMBNAIL := preload("res://scripts/shared/thumbnails/stickman_thumbnail.gd")
|
||||
const PROP_THUMBNAIL := preload("res://scripts/shared/thumbnails/prop_thumbnail.gd")
|
||||
|
||||
# Phase 3c editor tools.
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const TRIGGER_REGISTRY := preload("res://scripts/trigger_registry.gd")
|
||||
const QUEUE_PANEL := preload("res://scenes/queue_panel.tscn")
|
||||
const RULE_PANEL := preload("res://scenes/rule_panel.tscn")
|
||||
const ACTION_EDITOR := preload("res://scenes/action_editor.tscn")
|
||||
const SPEECH_COMPOSER := preload("res://scenes/speech_composer.tscn")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech_payload.gd")
|
||||
const SPEECH_STYLE_TOOLBAR := preload("res://scripts/speech_style_toolbar.gd")
|
||||
const SPEECH_SHOW_TOOLBAR := preload("res://scripts/speech_show_toolbar.gd")
|
||||
const RULE_EDITOR := preload("res://scenes/rule_editor.tscn")
|
||||
const WAYPOINT_CONTEXT := preload("res://scripts/waypoint_context.gd")
|
||||
const STAGE_SCENE_IO := preload("res://scripts/stage_scene_io.gd")
|
||||
const MISSING_STICKMAN_PLACEHOLDER := preload("res://scripts/missing_stickman_placeholder.gd")
|
||||
const MISSING_PROP_PLACEHOLDER := preload("res://scripts/missing_prop_placeholder.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const TRIGGER_REGISTRY := preload("res://scripts/stage/trigger_registry.gd")
|
||||
const QUEUE_PANEL := preload("res://scenes/editors/stage/queue_panel.tscn")
|
||||
const RULE_PANEL := preload("res://scenes/editors/stage/rule_panel.tscn")
|
||||
const ACTION_EDITOR := preload("res://scenes/editors/stage/action_editor.tscn")
|
||||
const SPEECH_COMPOSER := preload("res://scenes/editors/stage/speech_composer.tscn")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech/speech_payload.gd")
|
||||
const SPEECH_STYLE_TOOLBAR := preload("res://scripts/speech/speech_style_toolbar.gd")
|
||||
const SPEECH_SHOW_TOOLBAR := preload("res://scripts/speech/speech_show_toolbar.gd")
|
||||
const RULE_EDITOR := preload("res://scenes/editors/stage/rule_editor.tscn")
|
||||
const WAYPOINT_CONTEXT := preload("res://scripts/stage/waypoint_context.gd")
|
||||
const STAGE_SCENE_IO := preload("res://scripts/stage/stage_scene_io.gd")
|
||||
const CONTENT_PATHS := preload("res://scripts/shared/content_paths.gd")
|
||||
const MISSING_STICKMAN_PLACEHOLDER := preload("res://scripts/characters/stickman/missing_stickman_placeholder.gd")
|
||||
const MISSING_PROP_PLACEHOLDER := preload("res://scripts/props/missing_prop_placeholder.gd")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
@@ -1905,7 +1906,7 @@ func _place_at(world_pos: Vector2) -> void:
|
||||
|
||||
|
||||
## Common post-spawn wiring: freeze kinematic props in EDIT, save authored state,
|
||||
## and connect the Phase 3a/4 signals for stickmen/props.
|
||||
## and connect the Phase 3a/4 signals for content/stickmen/props.
|
||||
func _finalize_placed_node(node: Node2D) -> void:
|
||||
if node is PropBlock:
|
||||
var placed_prop := node as PropBlock
|
||||
@@ -2409,12 +2410,17 @@ func _collect_scene_data(is_autosave: bool = false) -> Dictionary:
|
||||
entry["facing_profile"] = StickmanRig.FacingProfile.keys()[int(authored.get("facing_profile", rig.get_facing_profile()))]
|
||||
var queue_result: Dictionary = STAGE_SCENE_IO.encode_queue(rig.get_queue())
|
||||
entry["action_queue"] = queue_result["actions"]
|
||||
if type == "stickman" and entry.has("stk_path"):
|
||||
entry["stk_path"] = CONTENT_PATHS.canonicalize(String(entry.get("stk_path", "")))
|
||||
elif type == "prop" and String(entry.get("asset_id", "")).ends_with(".prop"):
|
||||
entry["asset_id"] = CONTENT_PATHS.canonicalize(String(entry.get("asset_id", "")))
|
||||
objects.append(entry)
|
||||
var rules_result: Dictionary = STAGE_SCENE_IO.encode_rules(_event_rules, runtime_to_file)
|
||||
return {"version": STAGE_SCENE_IO.FILE_VERSION, "scene_name": _scene_name, "camera": {"position": STAGE_SCENE_IO.vector_to_data(_camera.global_position), "zoom": _camera.zoom.x}, "objects": objects, "event_rules": rules_result["rules"], "metadata": {"created_unix": _scene_created_unix if _scene_created_unix > 0 else Time.get_unix_time_from_system(), "modified_unix": Time.get_unix_time_from_system(), "autosave": is_autosave, "source_path": _current_scene_path if is_autosave else ""}}
|
||||
return {"version": STAGE_SCENE_IO.FILE_VERSION, "scene_name": _scene_name, "camera": {"position": STAGE_SCENE_IO.vector_to_data(_camera.global_position), "zoom": _camera.zoom.x}, "objects": objects, "event_rules": rules_result["rules"], "metadata": {"created_unix": _scene_created_unix if _scene_created_unix > 0 else Time.get_unix_time_from_system(), "modified_unix": Time.get_unix_time_from_system(), "autosave": is_autosave, "source_path": CONTENT_PATHS.canonicalize(_current_scene_path) if is_autosave else ""}}
|
||||
|
||||
|
||||
func _save_scene_to_path(path: String, is_autosave: bool = false) -> bool:
|
||||
path = CONTENT_PATHS.canonicalize(path)
|
||||
if _speech_composer_is_open():
|
||||
if is_autosave:
|
||||
return false
|
||||
@@ -2483,6 +2489,7 @@ func _reset_scene_content() -> void:
|
||||
|
||||
|
||||
func _load_scene_from_path(path: String, is_recovery: bool = false) -> bool:
|
||||
path = CONTENT_PATHS.canonicalize(path)
|
||||
var read: Dictionary = STAGE_SCENE_IO.read_file(path)
|
||||
if not bool(read.get("ok", false)):
|
||||
_show_toast(String(read.get("error", "Could not load scene."))); return false
|
||||
@@ -2490,12 +2497,17 @@ func _load_scene_from_path(path: String, is_recovery: bool = false) -> bool:
|
||||
|
||||
|
||||
func _apply_scene_data(data: Dictionary, source_path: String, is_recovery: bool) -> bool:
|
||||
source_path = CONTENT_PATHS.canonicalize(source_path)
|
||||
var was_suppressed := _suppress_dirty; _suppress_dirty = true
|
||||
_reset_scene_content()
|
||||
var file_to_runtime: Dictionary = {}; var unavailable: Dictionary = {}; var warnings: Array[String] = []; var missing_stickmen := 0; var missing_paths: Array[String] = []
|
||||
for raw: Variant in data.get("objects", []):
|
||||
if not raw is Dictionary: warnings.append("Malformed object"); continue
|
||||
var entry: Dictionary = raw; var file_id := String(entry.get("file_id", "")); var type := String(entry.get("type", ""))
|
||||
var entry: Dictionary = (raw as Dictionary).duplicate(true); var file_id := String(entry.get("file_id", "")); var type := String(entry.get("type", ""))
|
||||
if type == "stickman" and entry.has("stk_path"):
|
||||
entry["stk_path"] = CONTENT_PATHS.canonicalize(String(entry.get("stk_path", "")))
|
||||
elif type == "prop" and String(entry.get("asset_id", "")).ends_with(".prop"):
|
||||
entry["asset_id"] = CONTENT_PATHS.canonicalize(String(entry.get("asset_id", "")))
|
||||
if file_id.is_empty() or file_to_runtime.has(file_id) or unavailable.has(file_id): warnings.append("Duplicate or empty file id"); continue
|
||||
if not STAGE_SCENE_IO.is_vector_data(entry.get("position")) or not STAGE_SCENE_IO._finite_number(entry.get("rotation", 0.0)):
|
||||
warnings.append("Malformed object transform"); continue
|
||||
@@ -2577,7 +2589,7 @@ func _apply_scene_data(data: Dictionary, source_path: String, is_recovery: bool)
|
||||
var raw_metadata: Variant = data.get("metadata", {})
|
||||
var metadata: Dictionary = raw_metadata as Dictionary if raw_metadata is Dictionary else {}
|
||||
_scene_created_unix = int(metadata.get("created_unix", Time.get_unix_time_from_system()))
|
||||
_current_scene_path = String(metadata.get("source_path", source_path)) if is_recovery else source_path
|
||||
_current_scene_path = CONTENT_PATHS.canonicalize(String(metadata.get("source_path", source_path))) if is_recovery else source_path
|
||||
_rebuild_grid_cells(); _nav_dirty = true; _suppress_dirty = was_suppressed; _set_scene_dirty(is_recovery)
|
||||
if not warnings.is_empty() or int(decoded_rules.get("skipped", 0)) > 0 or missing_stickmen > 0:
|
||||
_show_toast("Loaded with %d warning(s), %d missing stickman, %d skipped rule(s)" % [warnings.size(), missing_stickmen, int(decoded_rules.get("skipped", 0))])
|
||||
@@ -2619,6 +2631,7 @@ func _show_missing_assets_dialog(paths: Array[String]) -> void:
|
||||
|
||||
|
||||
func _add_recent_scene(path: String) -> void:
|
||||
path = CONTENT_PATHS.canonicalize(path)
|
||||
if path.is_empty() or path == AUTOSAVE_PATH: return
|
||||
_recent_scenes.erase(path); _recent_scenes.push_front(path)
|
||||
while _recent_scenes.size() > RECENT_SCENE_LIMIT: _recent_scenes.pop_back()
|
||||
@@ -2633,8 +2646,9 @@ func _update_recent_menu() -> void:
|
||||
recent.clear()
|
||||
var existing: Array[String] = []
|
||||
for path: String in _recent_scenes:
|
||||
if FileAccess.file_exists(path): existing.append(path)
|
||||
var pruned := existing.size() != _recent_scenes.size()
|
||||
var canonical_path: String = CONTENT_PATHS.canonicalize(path)
|
||||
if FileAccess.file_exists(canonical_path) and not existing.has(canonical_path): existing.append(canonical_path)
|
||||
var pruned := existing != _recent_scenes
|
||||
_recent_scenes = existing
|
||||
if pruned: _save_settings()
|
||||
if _recent_scenes.is_empty():
|
||||
@@ -2670,7 +2684,7 @@ func _check_autosave_recovery() -> void:
|
||||
var metadata: Dictionary = raw_metadata as Dictionary if raw_metadata is Dictionary else {}
|
||||
if not bool(metadata.get("autosave", false)):
|
||||
DirAccess.remove_absolute(AUTOSAVE_PATH); return
|
||||
var source_path := String(metadata.get("source_path", ""))
|
||||
var source_path: String = CONTENT_PATHS.canonicalize(String(metadata.get("source_path", "")))
|
||||
if not source_path.is_empty() and FileAccess.file_exists(source_path) and int(metadata.get("modified_unix", 0)) <= int(FileAccess.get_modified_time(source_path)):
|
||||
DirAccess.remove_absolute(AUTOSAVE_PATH); return
|
||||
var dialog := ConfirmationDialog.new()
|
||||
@@ -3331,6 +3345,7 @@ func _build_ui() -> void:
|
||||
_browse_dialog.title = "Open .stk"
|
||||
_browse_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_browse_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_browse_dialog.current_dir = ProjectSettings.globalize_path(CONTENT_PATHS.STICKMEN_DIR)
|
||||
_browse_dialog.filters = PackedStringArray(["*.stk ; Stickman Files"])
|
||||
_browse_dialog.file_selected.connect(_on_browse_file_selected)
|
||||
ui.add_child(_browse_dialog)
|
||||
@@ -3339,6 +3354,7 @@ func _build_ui() -> void:
|
||||
_scene_open_dialog.name = "SceneOpenDialog"
|
||||
_scene_open_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_scene_open_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_scene_open_dialog.current_dir = ProjectSettings.globalize_path(CONTENT_PATHS.STAGES_DIR)
|
||||
_scene_open_dialog.filters = PackedStringArray(["*.scene ; Stickman Scene Files"])
|
||||
_scene_open_dialog.file_selected.connect(func(path: String) -> void: _load_scene_from_path(path))
|
||||
ui.add_child(_scene_open_dialog)
|
||||
@@ -3346,6 +3362,7 @@ func _build_ui() -> void:
|
||||
_scene_save_dialog.name = "SceneSaveDialog"
|
||||
_scene_save_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_scene_save_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||
_scene_save_dialog.current_dir = ProjectSettings.globalize_path(CONTENT_PATHS.STAGES_DIR)
|
||||
_scene_save_dialog.filters = PackedStringArray(["*.scene ; Stickman Scene Files"])
|
||||
_scene_save_dialog.file_selected.connect(_on_scene_save_path_selected)
|
||||
_scene_save_dialog.canceled.connect(func() -> void: _pending_file_action = PendingFileAction.NONE)
|
||||
@@ -5875,10 +5892,10 @@ func _finish_speech_composition(clear_recovery: bool = false) -> void:
|
||||
func _capture_speech_origin(actor: StickmanRig, context: String) -> Dictionary:
|
||||
var origin: Dictionary = {
|
||||
"context": context,
|
||||
"scene_path": _current_scene_path,
|
||||
"scene_path": CONTENT_PATHS.canonicalize(_current_scene_path),
|
||||
"scene_fingerprint": _speech_scene_fingerprint(),
|
||||
"actor_file_id": _speech_actor_file_id(actor),
|
||||
"actor_stk_path": actor.source_stk_path if actor != null and is_instance_valid(actor) else "",
|
||||
"actor_stk_path": CONTENT_PATHS.canonicalize(actor.source_stk_path) if actor != null and is_instance_valid(actor) else "",
|
||||
"action_editor_kind": _action_editor_kind,
|
||||
"action_editor_index": _action_editor_index,
|
||||
"restore_queue": _action_editor_restore_queue,
|
||||
@@ -5976,6 +5993,7 @@ func _queue_action_wire_at(actor: StickmanRig, index: int) -> Dictionary:
|
||||
|
||||
|
||||
func _validate_speech_origin(origin: Dictionary) -> Dictionary:
|
||||
origin = _canonicalize_speech_origin(origin)
|
||||
if _current_scene_path.is_empty() or String(origin.get("scene_path", "")) != _current_scene_path:
|
||||
return {}
|
||||
var scene_fingerprint_matches: bool = String(origin.get("scene_fingerprint", "")) == _speech_scene_fingerprint()
|
||||
@@ -6003,6 +6021,14 @@ func _validate_speech_origin(origin: Dictionary) -> Dictionary:
|
||||
return {"actor": actor, "context": "action_editor", "kind": editor_kind, "queue_index": queue_index}
|
||||
|
||||
|
||||
func _canonicalize_speech_origin(origin: Dictionary) -> Dictionary:
|
||||
var canonical: Dictionary = origin.duplicate(true)
|
||||
for key: String in ["scene_path", "actor_stk_path"]:
|
||||
if canonical.has(key):
|
||||
canonical[key] = CONTENT_PATHS.canonicalize(String(canonical.get(key, "")))
|
||||
return canonical
|
||||
|
||||
|
||||
func _check_speech_recovery() -> void:
|
||||
if _speech_recovery_dialog != null and _speech_recovery_matches_loaded_scene():
|
||||
_speech_recovery_dialog.popup_centered()
|
||||
@@ -6051,7 +6077,13 @@ func _read_speech_draft_document() -> Dictionary:
|
||||
return {}
|
||||
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
return parsed as Dictionary if parsed is Dictionary and int((parsed as Dictionary).get("schema", -1)) == SPEECH_DRAFT_SCHEMA else {}
|
||||
if not parsed is Dictionary or int((parsed as Dictionary).get("schema", -1)) != SPEECH_DRAFT_SCHEMA:
|
||||
return {}
|
||||
var document: Dictionary = (parsed as Dictionary).duplicate(true)
|
||||
var origin_value: Variant = document.get("origin", {})
|
||||
if origin_value is Dictionary:
|
||||
document["origin"] = _canonicalize_speech_origin(origin_value as Dictionary)
|
||||
return document
|
||||
|
||||
|
||||
func _on_speech_recovery_restore() -> void:
|
||||
@@ -6226,7 +6258,7 @@ func _persist_speech_draft_now() -> void:
|
||||
"modified_unix": Time.get_unix_time_from_system(),
|
||||
"draft": _speech_payload_for_json(_speech_composer.call("get_draft")),
|
||||
"selected_bubble": int(_speech_composer.call("selected_bubble_index")),
|
||||
"origin": _speech_origin.duplicate(true),
|
||||
"origin": _canonicalize_speech_origin(_speech_origin),
|
||||
}
|
||||
var tmp_path: String = SPEECH_DRAFT_PATH + ".tmp"
|
||||
var bak_path: String = SPEECH_DRAFT_PATH + ".bak"
|
||||
@@ -6742,12 +6774,17 @@ func _load_settings() -> void:
|
||||
_recent_scenes.clear()
|
||||
var raw_recents: Variant = d.get("recent_scenes", [])
|
||||
if raw_recents is Array:
|
||||
var settings_migrated: bool = false
|
||||
for raw: Variant in raw_recents:
|
||||
var path := String(raw)
|
||||
var old_path: String = String(raw)
|
||||
var path: String = CONTENT_PATHS.canonicalize(old_path)
|
||||
settings_migrated = settings_migrated or path != old_path
|
||||
if not path.is_empty() and FileAccess.file_exists(path) and not _recent_scenes.has(path):
|
||||
_recent_scenes.append(path)
|
||||
if _recent_scenes.size() >= RECENT_SCENE_LIMIT:
|
||||
break
|
||||
if settings_migrated:
|
||||
_save_settings()
|
||||
|
||||
|
||||
func _save_settings() -> void:
|
||||
@@ -2,9 +2,9 @@ class_name SpeechComposer
|
||||
extends Control
|
||||
## Stage-side comic speech editor. The stage owns the attached preview.
|
||||
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech_payload.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/sandbox_theme.gd")
|
||||
const SPEECH_BUBBLE := preload("res://scripts/stickman_speech_bubble.gd")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech/speech_payload.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/stage/sandbox_theme.gd")
|
||||
const SPEECH_BUBBLE := preload("res://scripts/speech/stickman_speech_bubble.gd")
|
||||
const FIVE_BUBBLE_HINT: String = "Long story? One at a time can be easier to follow."
|
||||
const MAX_BUBBLE_HINT: String = "You can add up to eight bubbles."
|
||||
|
||||
@@ -14,6 +14,8 @@ extends Control
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
const ContentPathsScript := preload("res://scripts/shared/content_paths.gd")
|
||||
|
||||
const BODY_PART_NAMES: PackedStringArray = [
|
||||
"head", "torso",
|
||||
"left_upper_arm", "left_lower_arm",
|
||||
@@ -53,7 +55,7 @@ const FILE_FILTER := "*.stk ; Stickman Files"
|
||||
|
||||
const SUPPORTED_VERSIONS: Array[String] = ["1.0", "1.1", "1.2", "1.3", "1.4", "1.5"]
|
||||
|
||||
# Phase 8: rig proportions (scenes/stickman_rig.tscn rest pose — see spec §2).
|
||||
# Phase 8: rig proportions (scenes/characters/stickman/stickman_rig.tscn rest pose — see spec §2).
|
||||
const PROPORTIONS: Dictionary = {
|
||||
"upper_arm_length": 168.0,
|
||||
"lower_arm_length": 200.0,
|
||||
@@ -199,6 +201,9 @@ func _setup_menu_bar() -> void:
|
||||
|
||||
|
||||
func _setup_dialogs() -> void:
|
||||
var content_dir: String = ProjectSettings.globalize_path(ContentPathsScript.STICKMEN_DIR)
|
||||
_save_dialog.current_dir = content_dir
|
||||
_load_dialog.current_dir = content_dir
|
||||
_save_dialog.file_selected.connect(_on_save_file_selected)
|
||||
_load_dialog.file_selected.connect(_on_load_file_selected)
|
||||
_clear_confirm.confirmed.connect(_on_clear_confirmed)
|
||||
@@ -211,7 +216,7 @@ func _populate_body_part_panels() -> void:
|
||||
var left_col: VBoxContainer = %LeftColumn
|
||||
var center_col: VBoxContainer = %CenterColumn
|
||||
|
||||
var body_part_scene: PackedScene = load("res://scenes/body_part_panel.tscn")
|
||||
var body_part_scene: PackedScene = load("res://scenes/editors/stickman/body_part_panel.tscn")
|
||||
|
||||
var left_names: Array[String] = ["head", "left_upper_arm", "left_lower_arm", "left_upper_leg", "left_lower_leg"]
|
||||
var center_names: Array[String] = ["torso", "right_upper_arm", "right_lower_arm", "right_upper_leg", "right_lower_leg"]
|
||||
@@ -314,6 +319,7 @@ func _on_grid_config_confirmed() -> void:
|
||||
|
||||
|
||||
func _on_save_file_selected(path: String) -> void:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
if not path.ends_with(".stk"):
|
||||
path += ".stk"
|
||||
|
||||
@@ -330,6 +336,7 @@ func _on_save_file_selected(path: String) -> void:
|
||||
|
||||
|
||||
func _on_load_file_selected(path: String) -> void:
|
||||
path = ContentPathsScript.canonicalize(path)
|
||||
if not FileAccess.file_exists(path):
|
||||
_show_error("File not found: " + path)
|
||||
return
|
||||
@@ -5,7 +5,7 @@ signal anchors_changed(anchors: Dictionary)
|
||||
signal anchor_selected(anchor_id: String)
|
||||
|
||||
enum DragMode { NONE, TRANSLATE, ROTATE }
|
||||
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const MARKER_RADIUS: float = 9.0
|
||||
const HIT_RADIUS: float = 15.0
|
||||
const ROTATE_OFFSET: float = 28.0
|
||||
@@ -10,8 +10,8 @@ extends RigidBody2D
|
||||
## properties update the children live. A material preset selector sets mass,
|
||||
## friction/bounce (PhysicsMaterial), and themed visuals.
|
||||
|
||||
const PropVisuals := preload("res://scripts/prop_visuals.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
|
||||
const PropVisuals := preload("res://scripts/props/prop_visuals.gd")
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
@@ -1,10 +1,11 @@
|
||||
class_name PropLibrary
|
||||
extends RefCounted
|
||||
|
||||
const PROP_UTILS := preload("res://scripts/prop_utils.gd")
|
||||
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
|
||||
const PROP_FILE := preload("res://scripts/prop_file.gd")
|
||||
const PROP_VISUALS := preload("res://scripts/prop_visuals.gd")
|
||||
const PROP_UTILS := preload("res://scripts/props/prop_utils.gd")
|
||||
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
|
||||
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
|
||||
const PROP_VISUALS := preload("res://scripts/props/prop_visuals.gd")
|
||||
const CONTENT_PATHS := preload("res://scripts/shared/content_paths.gd")
|
||||
|
||||
static var _templates: Array[Dictionary] = []
|
||||
|
||||
@@ -83,7 +84,7 @@ static func is_custom_path(path: String) -> bool:
|
||||
|
||||
|
||||
static func canonical_path(path: String) -> String:
|
||||
return path.replace("\\", "/").simplify_path()
|
||||
return CONTENT_PATHS.canonicalize(path.replace("\\", "/").simplify_path())
|
||||
|
||||
|
||||
static func get_default_id() -> String:
|
||||
@@ -13,8 +13,8 @@ extends RefCounted
|
||||
# registry, so this script compiles even when the editor's class cache is stale)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
const PropBlockScript := preload("res://scripts/prop_block.gd")
|
||||
const TerrainUtilsScript := preload("res://scripts/terrain_utils.gd")
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
const TerrainUtilsScript := preload("res://scripts/terrain/terrain_utils.gd")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Color themes
|
||||
@@ -0,0 +1,55 @@
|
||||
class_name ContentPaths
|
||||
extends RefCounted
|
||||
## Canonical project-managed content directories and legacy path migration.
|
||||
|
||||
const STICKMEN_DIR: String = "res://content/stickmen"
|
||||
const PROPS_DIR: String = "res://content/props"
|
||||
const STAGES_DIR: String = "res://content/stages"
|
||||
|
||||
const LEGACY_DIRS: Dictionary = {
|
||||
"stickmen": "content/stickmen",
|
||||
"props": "content/props",
|
||||
"game_scenes": "content/stages",
|
||||
}
|
||||
|
||||
|
||||
static func canonicalize(path: String) -> String:
|
||||
if path.is_empty():
|
||||
return path
|
||||
var normalized: String = path.replace("\\", "/")
|
||||
if normalized.begins_with("res://"):
|
||||
var relative_path: String = normalized.substr("res://".length())
|
||||
var migrated: String = _canonicalize_relative(relative_path)
|
||||
return "res://" + migrated if migrated != relative_path else path
|
||||
if normalized.begins_with("user://"):
|
||||
return path
|
||||
if normalized.is_absolute_path():
|
||||
return _canonicalize_absolute(normalized, path)
|
||||
var migrated_relative: String = _canonicalize_relative(normalized)
|
||||
return migrated_relative if migrated_relative != normalized else path
|
||||
|
||||
|
||||
static func _canonicalize_relative(path: String) -> String:
|
||||
var normalized: String = path.simplify_path()
|
||||
for legacy_dir: String in LEGACY_DIRS:
|
||||
var canonical_dir: String = String(LEGACY_DIRS[legacy_dir])
|
||||
if normalized == legacy_dir:
|
||||
return canonical_dir
|
||||
var prefix: String = legacy_dir + "/"
|
||||
if normalized.begins_with(prefix):
|
||||
return canonical_dir + "/" + normalized.substr(prefix.length())
|
||||
return path
|
||||
|
||||
|
||||
static func _canonicalize_absolute(path: String, original_path: String) -> String:
|
||||
var project_root: String = ProjectSettings.globalize_path("res://").replace("\\", "/").trim_suffix("/")
|
||||
if path == project_root:
|
||||
return original_path
|
||||
var project_prefix: String = project_root + "/"
|
||||
if not path.begins_with(project_prefix):
|
||||
return original_path
|
||||
var relative_path: String = path.substr(project_prefix.length())
|
||||
var migrated: String = _canonicalize_relative(relative_path)
|
||||
if migrated == relative_path:
|
||||
return original_path
|
||||
return project_root.path_join(migrated)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d4im476gdaq6j
|
||||
@@ -1,8 +1,8 @@
|
||||
class_name PropThumbnail
|
||||
extends Node
|
||||
|
||||
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
|
||||
const PROP_VISUALS := preload("res://scripts/prop_visuals.gd")
|
||||
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
|
||||
const PROP_VISUALS := preload("res://scripts/props/prop_visuals.gd")
|
||||
|
||||
const SIZE := Vector2i(200, 200)
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
class_name StickmanThumbnail
|
||||
extends Node
|
||||
|
||||
const STICKMAN_FACTORY := preload("res://scripts/stickman_factory.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
|
||||
const STICKMAN_FACTORY := preload("res://scripts/characters/stickman/stickman_factory.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
|
||||
|
||||
const SIZE := Vector2i(200, 200)
|
||||
|
||||
@@ -12,7 +12,7 @@ const PAGE_SIZE := COLUMNS * ROWS
|
||||
|
||||
const CELL_MIN_SIZE := Vector2(190.0, 220.0)
|
||||
const THUMB_SIZE := Vector2(156.0, 156.0)
|
||||
const ASSET_CARD := preload("res://scripts/ui/asset_card.gd")
|
||||
const ASSET_CARD := preload("res://scripts/shared/ui/asset_card.gd")
|
||||
|
||||
var kind: String = ""
|
||||
|
||||
@@ -2,11 +2,11 @@ class_name SpeechStyleToolbar
|
||||
extends Control
|
||||
## Transient stage-side speech styling controls. The composer owns every edit.
|
||||
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech_payload.gd")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech/speech_payload.gd")
|
||||
|
||||
|
||||
class FramePreview extends Control:
|
||||
const RENDERER := preload("res://scripts/stickman_speech_bubble.gd")
|
||||
const RENDERER := preload("res://scripts/speech/stickman_speech_bubble.gd")
|
||||
var frame_id: String = "talk"
|
||||
var fill: Color = Color.WHITE
|
||||
var outline: Color = Color.BLACK
|
||||
@@ -18,10 +18,10 @@ class EditorOverlayLayer extends Node2D:
|
||||
if renderer != null and is_instance_valid(renderer):
|
||||
renderer.call("_draw_editor_overlay_layer", self)
|
||||
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech_payload.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/sandbox_theme.gd")
|
||||
const SPEECH_WORD_EFFECT := preload("res://scripts/speech_word_effect.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd")
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech/speech_payload.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/stage/sandbox_theme.gd")
|
||||
const SPEECH_WORD_EFFECT := preload("res://scripts/speech/speech_word_effect.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
|
||||
const AUTO_HEAD_GAP: float = 48.0
|
||||
const MAX_FRAME_WIDTH: float = 420.0
|
||||
const MIN_FRAME_WIDTH: float = 108.0
|
||||
@@ -6,8 +6,8 @@ extends Node2D
|
||||
## connectors plus small badges for speak/wait/ragdoll/recover, with order
|
||||
## numbers. Pure drawing; no hit-testing. Visible in EDIT, hidden in PLAY.
|
||||
|
||||
const STICKMAN_RIG := preload("res://scripts/stickman_rig.gd")
|
||||
const ACTION_RUNNER := preload("res://scripts/stickman_action_runner.gd")
|
||||
const STICKMAN_RIG := preload("res://scripts/characters/stickman/stickman_rig.gd")
|
||||
const ACTION_RUNNER := preload("res://scripts/characters/stickman/stickman_action_runner.gd")
|
||||
|
||||
const WAYPOINT_RADIUS_PX := 6.0
|
||||
const WAYPOINT_COLOR := Color(0.2, 0.5, 1.0)
|
||||
@@ -9,8 +9,8 @@ extends Node2D
|
||||
## distance-based hit-testing (no Area2D). Dragging drives global_position /
|
||||
## global_rotation so it works for any Node2D. Hidden and disabled in PLAY mode.
|
||||
|
||||
const STAGE_SELECTION := preload("res://scripts/stage_selection.gd")
|
||||
const PropBlockScript := preload("res://scripts/prop_block.gd")
|
||||
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user