diff --git a/action_class_design.md b/action_class_design.md
deleted file mode 100644
index 63f2bc4..0000000
--- a/action_class_design.md
+++ /dev/null
@@ -1,116 +0,0 @@
-# Base Action Class Design
-
-## Overview
-The `Action` class is the abstract base class for all cutscene actions. It defines the common interface and functionality that all actions must implement.
-
-## Class Structure
-
-```gdscript
-class_name Action
-extends RefCounted
-
-# Action states
-enum State {
- PENDING, # Action is waiting to be executed
- RUNNING, # Action is currently executing
- COMPLETED # Action has finished executing
-}
-
-# Public properties
-var state: int = State.PENDING
-var name: String = "Action"
-
-# Signals
-signal started() # Emitted when action starts
-signal completed() # Emitted when action completes
-signal failed(error) # Emitted if action fails
-
-# Virtual methods to be implemented by subclasses
-func start() -> void:
- # Start executing the action
- # This method should be overridden by subclasses
- pass
-
-func update(delta: float) -> void:
- # Update the action (called every frame while running)
- # This method can be overridden by subclasses
- pass
-
-func is_completed() -> bool:
- # Check if the action has completed
- # This method should be overridden by subclasses
- return state == State.COMPLETED
-
-func stop() -> void:
- # Stop the action (if it's running)
- # This method can be overridden by subclasses
- pass
-
-# Helper methods
-func _set_running() -> void:
- # Set the action state to RUNNING and emit started signal
- state = State.RUNNING
- started.emit()
-
-func _set_completed() -> void:
- # Set the action state to COMPLETED and emit completed signal
- state = State.COMPLETED
- completed.emit()
-
-func _set_failed(error_message: String) -> void:
- # Set the action state to FAILED and emit failed signal
- state = State.FAILED
- failed.emit(error_message)
-```
-
-## Implementation Guidelines
-
-### Required Methods
-All action subclasses must implement these methods:
-
-1. `start()` - Initialize and begin the action
-2. `is_completed()` - Return true when the action is finished
-3. `update(delta)` - Optional, for frame-based updates
-
-### State Management
-Actions should properly manage their state:
-- Start in `PENDING` state
-- Move to `RUNNING` when `start()` is called
-- Move to `COMPLETED` when finished
-- Emit appropriate signals for state changes
-
-### Signal Usage
-Actions should emit these signals:
-- `started()` when the action begins
-- `completed()` when the action finishes successfully
-- `failed(error)` when the action encounters an error
-
-## Example Implementation
-
-```gdscript
-# Example of a simple WaitAction implementation
-class_name WaitAction
-extends Action
-
-var duration: float
-var elapsed_time: float = 0.0
-
-func _init(wait_duration: float):
- duration = wait_duration
- name = "WaitAction"
-
-func start() -> void:
- ._set_running()
- elapsed_time = 0.0
-
-func update(delta: float) -> void:
- if state == State.RUNNING:
- elapsed_time += delta
- if elapsed_time >= duration:
- ._set_completed()
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-This design provides a solid foundation for all action types while maintaining flexibility for different implementation needs.
\ No newline at end of file
diff --git a/action_completion_system.md b/action_completion_system.md
deleted file mode 100644
index 1905393..0000000
--- a/action_completion_system.md
+++ /dev/null
@@ -1,275 +0,0 @@
-# Action Completion and Callback System Design
-
-## Overview
-This document details the completion and callback system that allows actions to notify the CutsceneManager when they have finished executing, and enables flexible callback mechanisms for complex action sequences.
-
-## Signal-Based Completion System
-
-### Core Signals
-Each action implements these core signals:
-
-```gdscript
-# Emitted when the action starts executing
-signal started()
-
-# Emitted when the action completes successfully
-signal completed()
-
-# Emitted when the action fails
-signal failed(error_message)
-
-# Emitted when the action is manually stopped
-signal stopped()
-```
-
-### Signal Connection Flow
-1. CutsceneManager creates an action
-2. CutsceneManager connects to the action's signals
-3. When action starts, it emits `started()`
-4. When action completes, it emits `completed()`
-5. CutsceneManager's signal handlers update the cutscene state
-
-```gdscript
-# In CutsceneManager
-func add_action(action: Action) -> void:
- # Connect to action signals
- action.connect("started", _on_action_started)
- action.connect("completed", _on_action_completed)
- action.connect("failed", _on_action_failed)
- action.connect("stopped", _on_action_stopped)
-
- # Add to queue
- sequential_actions.append(action)
-
-func _on_action_started(action: Action) -> void:
- emit_signal("action_started", action)
-
-func _on_action_completed(action: Action) -> void:
- # Handle action completion
- # Move to next action or complete cutscene
- _handle_action_completion(action)
-
-func _on_action_failed(action: Action, error_message: String) -> void:
- # Handle action failure
- print("Action failed: %s - %s" % [action.name, error_message])
- # Stop the cutscene or handle error appropriately
- stop()
-
-func _on_action_stopped(action: Action) -> void:
- # Handle action being stopped
- _handle_action_completion(action)
-```
-
-## Parallel Action Group Completion
-
-For parallel actions, the system tracks completion of all actions in a group:
-
-```gdscript
-# Structure for tracking parallel action groups
-{
- "actions": [], # Array of actions in this group
- "completed_count": 0, # Number of completed actions
- "total_count": 0, # Total number of actions
- "on_complete": Callable # Optional callback when group completes
-}
-
-func add_parallel_actions(actions: Array, on_complete: Callable = Callable()) -> void:
- # Create a parallel action group
- var group = {
- "actions": actions,
- "completed_count": 0,
- "total_count": actions.size(),
- "on_complete": on_complete
- }
-
- # Connect to each action's completed signal
- for action in actions:
- action.connect("completed", _on_parallel_action_completed.bind(group, action))
- action.connect("failed", _on_parallel_action_failed.bind(group, action))
-
- # Add to active parallel groups
- active_parallel_groups.append(group)
-
- # Add to sequential queue as a single item
- sequential_actions.append(group)
-
-func _on_parallel_action_completed(group: Dictionary, action: Action) -> void:
- # Increment completed count
- group["completed_count"] += 1
-
- # Check if all actions in group are completed
- if group["completed_count"] >= group["total_count"]:
- # Remove from active groups
- active_parallel_groups.erase(group)
-
- # Call optional completion callback
- if group["on_complete"].is_valid():
- group["on_complete"].call()
-
- # Continue with next sequential action
- _execute_next_action()
-```
-
-## Callback System
-
-### Action-Level Callbacks
-Actions can have callbacks for specific events:
-
-```gdscript
-class_name Action
-extends RefCounted
-
-# Callback properties
-var on_started: Callable = Callable()
-var on_completed: Callable = Callable()
-var on_failed: Callable = Callable()
-
-func start() -> void:
- ._set_running()
-
- # Call started callback if valid
- if on_started.is_valid():
- on_started.call(self)
-
-func _set_completed() -> void:
- state = State.COMPLETED
- completed.emit()
-
- # Call completed callback if valid
- if on_completed.is_valid():
- on_completed.call(self)
-
-func _set_failed(error_message: String) -> void:
- state = State.FAILED
- failed.emit(error_message)
-
- # Call failed callback if valid
- if on_failed.is_valid():
- on_failed.call(self, error_message)
-```
-
-### Cutscene-Level Callbacks
-The CutsceneManager supports callbacks for cutscene events:
-
-```gdscript
-class_name CutsceneManager
-extends Node
-
-# Cutscene callbacks
-var on_started: Callable = Callable()
-var on_completed: Callable = Callable()
-var on_paused: Callable = Callable()
-var on_resumed: Callable = Callable()
-
-func start() -> void:
- if state != State.IDLE:
- return
-
- state = State.RUNNING
- is_active = true
- emit_signal("cutscene_started")
-
- # Call started callback
- if on_started.is_valid():
- on_started.call()
-
- # Start first action
- _execute_next_action()
-
-func _on_cutscene_completed() -> void:
- state = State.IDLE
- is_active = false
- emit_signal("cutscene_completed")
-
- # Call completed callback
- if on_completed.is_valid():
- on_completed.call()
-```
-
-## Chaining Actions with Callbacks
-
-Actions can be chained together using callbacks:
-
-```gdscript
-# Example: Create a sequence using callbacks
-func create_chained_sequence() -> void:
- var action1 = MoveAction.new(character1, Vector2(100, 100))
- var action2 = DialogueAction.new(character1, "I have arrived!")
- var action3 = AnimationAction.new(character1, "celebrate")
-
- # Chain actions using callbacks
- action1.on_completed = func(action):
- _execute_action(action2)
-
- action2.on_completed = func(action):
- _execute_action(action3)
-
- # Start the sequence
- _execute_action(action1)
-```
-
-## Error Handling and Recovery
-
-The system includes robust error handling:
-
-```gdscript
-# Action failure handling
-func _on_action_failed(action: Action, error_message: String) -> void:
- print("Action failed: %s - %s" % [action.name, error_message])
-
- # Emit a general failure signal
- emit_signal("action_failed", action, error_message)
-
- # Decide how to handle the failure:
- # 1. Stop the entire cutscene
- # 2. Skip the failed action and continue
- # 3. Retry the action
- # 4. Execute an alternative action
-
- # For now, we'll stop the cutscene
- stop()
-
-# Optional retry mechanism
-func _retry_action(action: Action, max_retries: int = 3) -> void:
- if not action.has_method("retry_count"):
- action.retry_count = 0
-
- action.retry_count += 1
-
- if action.retry_count <= max_retries:
- print("Retrying action: %s (attempt %d)" % [action.name, action.retry_count])
- action.start()
- else:
- print("Max retries exceeded for action: %s" % action.name)
- _on_action_failed(action, "Max retries exceeded")
-```
-
-## Integration with Godot's Signal System
-
-The completion system fully integrates with Godot's signal system:
-
-1. Uses Godot's built-in signal connection mechanisms
-2. Supports both method and callable-based connections
-3. Handles disconnection automatically when actions are removed
-4. Provides type safety through signal parameters
-
-```gdscript
-# Example of connecting to cutscene signals from outside
-func setup_cutscene() -> void:
- var cutscene = CutsceneManager.new()
-
- # Connect to cutscene signals
- cutscene.connect("cutscene_started", _on_cutscene_started)
- cutscene.connect("cutscene_completed", _on_cutscene_completed)
- cutscene.connect("action_started", _on_action_started)
- cutscene.connect("action_completed", _on_action_completed)
-
- # Configure callbacks
- cutscene.on_completed = func():
- print("Cutscene finished, returning to gameplay")
- # Return to gameplay state
-
- return cutscene
-```
-
-This completion and callback system provides a flexible, robust foundation for managing action completion in cutscenes while maintaining clean separation of concerns and enabling complex action sequences.
\ No newline at end of file
diff --git a/action_types_design.md b/action_types_design.md
deleted file mode 100644
index a14dc2e..0000000
--- a/action_types_design.md
+++ /dev/null
@@ -1,313 +0,0 @@
-# Action Types Design
-
-## Overview
-This document details the specific action types that will be implemented for the cutscene system. Each action type handles a specific kind of operation in a cutscene.
-
-## Core Action Types
-
-### 1. MoveAction
-Moves a character to a specific position over time.
-
-```gdscript
-class_name MoveAction
-extends Action
-
-# Properties
-var character: Node2D # The character to move
-var target_position: Vector2 # Target position
-var speed: float = 100.0 # Movement speed (pixels/second)
-var threshold: float = 5.0 # Distance threshold for completion
-
-# Internal
-var start_position: Vector2
-var distance: float
-var traveled: float = 0.0
-
-func _init(character_node: Node2D, target: Vector2, move_speed: float = 100.0):
- character = character_node
- target_position = target
- speed = move_speed
- name = "MoveAction"
-
-func start() -> void:
- if character == null:
- ._set_failed("Character is null")
- return
-
- start_position = character.position
- distance = start_position.distance_to(target_position)
- traveled = 0.0
- ._set_running()
-
-func update(delta: float) -> void:
- if state != State.RUNNING:
- return
-
- if character == null:
- ._set_failed("Character was destroyed during action")
- return
-
- # Calculate movement for this frame
- var frame_distance = speed * delta
- traveled += frame_distance
-
- # If we've reached or overshot the target
- if traveled >= distance:
- character.position = target_position
- ._set_completed()
- return
-
- # Move character along the path
- var direction = (target_position - start_position).normalized()
- character.position = start_position + direction * traveled
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-### 2. TurnAction
-Makes a character turn to face a direction or another character.
-
-```gdscript
-class_name TurnAction
-extends Action
-
-# Properties
-var character: Node2D # The character to turn
-var target: Variant # Either a Vector2 position or another Node2D
-var turn_speed: float = 2.0 # Rotation speed (radians/second)
-
-func _init(character_node: Node2D, turn_target: Variant, speed: float = 2.0):
- character = character_node
- target = turn_target
- turn_speed = speed
- name = "TurnAction"
-
-func start() -> void:
- if character == null:
- ._set_failed("Character is null")
- return
- ._set_running()
-
-func update(delta: float) -> void:
- if state != State.RUNNING:
- return
-
- if character == null:
- ._set_failed("Character was destroyed during action")
- return
-
- # Calculate target rotation
- var target_position: Vector2
- if target is Vector2:
- target_position = target
- elif target is Node2D:
- target_position = target.position
- else:
- ._set_failed("Invalid target type")
- return
-
- var direction = target_position - character.position
- var target_rotation = atan2(direction.y, direction.x)
-
- # Rotate toward target
- var angle_diff = angle_difference(character.rotation, target_rotation)
-
- # If we're close enough, complete the action
- if abs(angle_diff) < 0.01:
- character.rotation = target_rotation
- ._set_completed()
- return
-
- # Rotate based on turn speed
- var rotation_amount = sign(angle_diff) * turn_speed * delta
- if abs(rotation_amount) > abs(angle_diff):
- character.rotation = target_rotation
- ._set_completed()
- else:
- character.rotation += rotation_amount
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-### 3. DialogueAction
-Displays dialogue text, potentially with character-specific formatting.
-
-```gdscript
-class_name DialogueAction
-extends Action
-
-# Properties
-var character: Node2D # The speaking character (optional)
-var text: String # Dialogue text
-var duration: float = 0.0 # Duration to display (0 = manual advance)
-var auto_advance: bool = true # Whether to auto-advance after duration
-
-func _init(speaking_character: Node2D, dialogue_text: String, display_duration: float = 0.0):
- character = speaking_character
- text = dialogue_text
- duration = display_duration
- name = "DialogueAction"
-
-func start() -> void:
- # In a real implementation, this would interface with a dialogue system
- # For now, we'll simulate by printing to console
- print("%s: %s" % [character.name if character else "Narrator", text])
- ._set_running()
-
- # If duration is 0, this is a manual advance action
- # In a real game, this would wait for player input
- if duration <= 0:
- # For demo purposes, we'll complete immediately
- # In a real game, this would wait for input
- ._set_completed()
- else:
- # Start a timer for automatic completion
- var timer = Timer.new()
- timer.wait_time = duration
- timer.one_shot = true
- timer.connect("timeout", _on_timer_timeout)
- # In a real implementation, we'd add this to the scene tree
- # add_child(timer)
- timer.start()
-
-func _on_timer_timeout():
- ._set_completed()
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-### 4. AnimationAction
-Plays a specific animation on a character.
-
-```gdscript
-class_name AnimationAction
-extends Action
-
-# Properties
-var character: Node2D # The character to animate
-var animation_name: String # Name of the animation to play
-var loop: bool = false # Whether to loop the animation
-
-func _init(character_node: Node2D, anim_name: String, should_loop: bool = false):
- character = character_node
- animation_name = anim_name
- loop = should_loop
- name = "AnimationAction"
-
-func start() -> void:
- if character == null:
- ._set_failed("Character is null")
- return
-
- # In a real implementation, this would interface with an AnimationPlayer
- # For now, we'll simulate by printing to console
- print("Playing animation '%s' on %s" % [animation_name, character.name])
-
- # Check if character has an AnimationPlayer
- var anim_player = _get_animation_player()
- if anim_player == null:
- ._set_failed("Character has no AnimationPlayer")
- return
-
- # Connect to animation finished signal for non-looping animations
- if not loop:
- if not anim_player.is_connected("animation_finished", _on_animation_finished):
- anim_player.connect("animation_finished", _on_animation_finished)
-
- # Play the animation
- anim_player.play(animation_name, loop)
- ._set_running()
-
- # For looping animations, we complete immediately
- # (they would be stopped by another action)
- if loop:
- ._set_completed()
-
-func _get_animation_player() -> AnimationPlayer:
- # Try to find AnimationPlayer as a child
- for child in character.get_children():
- if child is AnimationPlayer:
- return child
-
- # Try to find AnimationPlayer in the scene tree
- return character.get_node_or_null("AnimationPlayer") as AnimationPlayer
-
-func _on_animation_finished(anim_name: String):
- if anim_name == animation_name:
- ._set_completed()
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-### 5. WaitAction
-Pauses execution for a specified time.
-
-```gdscript
-class_name WaitAction
-extends Action
-
-# Properties
-var duration: float # Time to wait in seconds
-var elapsed_time: float = 0.0
-
-func _init(wait_duration: float):
- duration = wait_duration
- name = "WaitAction"
-
-func start() -> void:
- elapsed_time = 0.0
- ._set_running()
-
-func update(delta: float) -> void:
- if state != State.RUNNING:
- return
-
- elapsed_time += delta
- if elapsed_time >= duration:
- ._set_completed()
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-## Extensibility
-
-To create new action types:
-
-1. Create a new class that extends `Action`
-2. Implement the required methods:
- - `start()` - Initialize and begin the action
- - `update(delta)` - Update the action each frame (if needed)
- - `is_completed()` - Return true when the action is finished
-3. Optionally implement `stop()` for cleanup
-4. Use appropriate signals to notify completion
-
-Example of a custom action:
-
-```gdscript
-class_name CustomAction
-extends Action
-
-# Custom properties for this action
-var custom_parameter: String
-
-func _init(param: String):
- custom_parameter = param
- name = "CustomAction"
-
-func start() -> void:
- # Perform the action
- print("Executing custom action with parameter: %s" % custom_parameter)
-
- # For immediate actions, complete right away
- ._set_completed()
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-This design provides a solid set of core action types while maintaining the flexibility to add new types as needed.
\ No newline at end of file
diff --git a/addons/cutscene_editor/CutsceneEditorPlugin.gd b/addons/cutscene_editor/CutsceneEditorPlugin.gd
new file mode 100644
index 0000000..ca34657
--- /dev/null
+++ b/addons/cutscene_editor/CutsceneEditorPlugin.gd
@@ -0,0 +1,215 @@
+@tool
+extends EditorPlugin
+
+# Main plugin script for the cutscene editor
+
+var dock_panel: Control
+var graph_edit: GraphEdit
+
+func _enter_tree() -> void:
+ # Initialize the plugin when it's enabled
+ _setup_plugin()
+
+ # Add custom types for cutscene resources
+ add_custom_type("CutsceneResource", "Resource", preload("res://addons/cutscene_editor/editor/resources/CutsceneResource.gd"),
+ preload("res://addons/cutscene_editor/icons/icon_entry.svg"))
+
+func _exit_tree() -> void:
+ # Clean up when the plugin is disabled
+ _cleanup_plugin()
+
+ # Remove custom types
+ remove_custom_type("CutsceneResource")
+
+func _setup_plugin() -> void:
+ # Create the main dock panel
+ dock_panel = _create_dock_panel()
+
+ # Add the dock panel to the editor
+ add_control_to_bottom_panel(dock_panel, "Cutscene Editor")
+
+ # Register the custom inspector plugin
+ #var inspector_plugin = preload("res://addons/cutscene_editor/editor/inspectors/CutsceneInspectorPlugin.gd").new()
+ #add_inspector_plugin(inspector_plugin)
+
+func _cleanup_plugin() -> void:
+ # Remove the dock panel from the editor
+ if dock_panel:
+ remove_control_from_bottom_panel(dock_panel)
+ dock_panel.queue_free()
+
+ # Remove inspector plugin
+ # Note: Inspector plugins are automatically removed when the plugin is disabled
+
+func _create_dock_panel() -> Control:
+ # Create the main dock panel UI
+ var panel = PanelContainer.new()
+
+ # Create a main container for the editor
+ var main_vbox = VBoxContainer.new()
+ main_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ main_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ panel.add_child(main_vbox)
+
+ # Create toolbar
+ var toolbar = _create_toolbar()
+ main_vbox.add_child(toolbar)
+
+ # Create graph edit area
+ graph_edit = _create_graph_edit()
+ main_vbox.add_child(graph_edit)
+
+ # Set up preview system
+ _setup_preview_system()
+
+ # Set up undo/redo system
+ _setup_undo_redo_system()
+
+ return panel
+
+func _create_toolbar() -> Control:
+ # Create the main toolbar with common actions
+ var toolbar = HBoxContainer.new()
+
+ # New button
+ var new_button = Button.new()
+ new_button.text = "New"
+ new_button.icon = get_editor_interface().get_base_control().get_theme_icon("New", "EditorIcons")
+ new_button.connect("pressed", _on_new_pressed)
+ toolbar.add_child(new_button)
+
+ # Open button
+ var open_button = Button.new()
+ open_button.text = "Open"
+ open_button.icon = get_editor_interface().get_base_control().get_theme_icon("Load", "EditorIcons")
+ open_button.connect("pressed", _on_open_pressed)
+ toolbar.add_child(open_button)
+
+ # Save button
+ var save_button = Button.new()
+ save_button.text = "Save"
+ save_button.icon = get_editor_interface().get_base_control().get_theme_icon("Save", "EditorIcons")
+ save_button.connect("pressed", _on_save_pressed)
+ toolbar.add_child(save_button)
+
+ # Separator
+ var separator1 = VSeparator.new()
+ toolbar.add_child(separator1)
+
+ # Undo button
+ var undo_button = Button.new()
+ undo_button.text = "Undo"
+ undo_button.icon = get_editor_interface().get_base_control().get_theme_icon("Undo", "EditorIcons")
+ undo_button.connect("pressed", _on_undo_pressed)
+ toolbar.add_child(undo_button)
+
+ # Redo button
+ var redo_button = Button.new()
+ redo_button.text = "Redo"
+ redo_button.icon = get_editor_interface().get_base_control().get_theme_icon("Redo", "EditorIcons")
+ redo_button.connect("pressed", _on_redo_pressed)
+ toolbar.add_child(redo_button)
+
+ # Separator
+ var separator2 = VSeparator.new()
+ toolbar.add_child(separator2)
+
+ # Preview button
+ var preview_button = Button.new()
+ preview_button.text = "Preview"
+ preview_button.icon = get_editor_interface().get_base_control().get_theme_icon("Play", "EditorIcons")
+ preview_button.connect("pressed", _on_preview_pressed)
+ toolbar.add_child(preview_button)
+
+ return toolbar
+
+func _create_graph_edit() -> GraphEdit:
+ # Create the main graph edit component
+ var graph = preload("res://addons/cutscene_editor/editor/CutsceneGraphEdit.gd").new()
+ graph.name = "CutsceneGraphEdit"
+ graph.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ graph.size_flags_vertical = Control.SIZE_EXPAND_FILL
+
+ # Set up graph properties
+ graph.set_right_disconnects(true)
+ graph.set_show_grid(true)
+ graph.set_snapping_enabled(true)
+ graph.set_snapping_distance(20)
+ graph.set_minimap_enabled(true)
+ graph.set_minimap_size(Vector2(200, 150))
+
+ # Connect signals
+ graph.connect("connection_request", graph._on_connection_request)
+ graph.connect("disconnection_request", graph._on_disconnection_request)
+ graph.connect("node_selected", graph._on_node_selected)
+ graph.connect("node_unselected", graph._on_node_unselected)
+ graph.connect("popup_request", graph._on_popup_request)
+ graph.connect("connection_to_empty", graph._on_connection_to_empty)
+ graph.connect("connection_from_empty", graph._on_connection_from_empty)
+
+ return graph
+
+func _setup_preview_system() -> void:
+ # Set up the preview system
+ if graph_edit:
+ graph_edit.setup_preview()
+
+func _setup_undo_redo_system() -> void:
+ # Set up the undo/redo system
+ if graph_edit:
+ graph_edit._setup_undo_redo()
+
+# Toolbar button handlers
+func _on_new_pressed() -> void:
+ if graph_edit:
+ graph_edit.clear_graph()
+
+func _on_open_pressed() -> void:
+ # Open file dialog to load a cutscene
+ var file_dialog = EditorFileDialog.new()
+ file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
+ file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
+ file_dialog.filters = ["*.tres", "*.res"]
+ file_dialog.connect("file_selected", _on_file_selected)
+
+ # Add to editor interface
+ get_editor_interface().get_base_control().add_child(file_dialog)
+ file_dialog.popup_centered_ratio()
+
+func _on_file_selected(path: String) -> void:
+ # Load the selected cutscene file
+ if graph_edit:
+ var cutscene_resource = load(path)
+ if cutscene_resource and cutscene_resource is CutsceneResource: #preload("res://addons/cutscene_editor/editor/resources/CutsceneResource.gd"):
+ graph_edit.load_from_cutscene(cutscene_resource)
+
+func _on_save_pressed() -> void:
+ # Open file dialog to save a cutscene
+ var file_dialog = EditorFileDialog.new()
+ file_dialog.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE
+ file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
+ file_dialog.filters = ["*.tres", "*.res"]
+ file_dialog.connect("file_selected", _on_save_file_selected)
+
+ # Add to editor interface
+ get_editor_interface().get_base_control().add_child(file_dialog)
+ file_dialog.popup_centered_ratio()
+
+func _on_save_file_selected(path: String) -> void:
+ # Save the current cutscene to the selected file
+ if graph_edit:
+ var cutscene_resource = graph_edit.save_to_cutscene()
+ if cutscene_resource:
+ ResourceSaver.save(cutscene_resource, path)
+
+func _on_undo_pressed() -> void:
+ if graph_edit:
+ graph_edit.undo()
+
+func _on_redo_pressed() -> void:
+ if graph_edit:
+ graph_edit.redo()
+
+func _on_preview_pressed() -> void:
+ if graph_edit:
+ graph_edit.start_preview()
diff --git a/addons/cutscene_editor/README.md b/addons/cutscene_editor/README.md
new file mode 100644
index 0000000..a93920e
--- /dev/null
+++ b/addons/cutscene_editor/README.md
@@ -0,0 +1,59 @@
+# Cutscene Editor Plugin
+
+A visual graph-based editor for designing point-and-click adventure game cutscenes in Godot 4.
+
+## Overview
+
+The Cutscene Editor Plugin provides a node-based interface for creating complex cutscene sequences without manual coding. Users can configure narrative sequences through visual connections that mirror the action-attachment functionality of the underlying cutscene system.
+
+## Features
+
+- **Visual Node-Based Interface**: Drag-and-drop nodes for creating cutscenes
+- **Action Representation**: Visual nodes for all core action types (Move, Turn, Dialogue, Animation, Wait)
+- **Parallel Execution**: Special Parallel Group nodes for simultaneous actions
+- **Connection System**: Visual connections for sequential and parallel execution flow
+- **Property Editing**: Inline parameter editing within nodes
+- **Real-Time Preview**: Immediate visualization of cutscene execution
+- **Undo/Redo System**: Comprehensive action reversal capabilities
+- **Save/Load Functionality**: Persistent storage of cutscene designs
+
+## Node Types
+
+- **Entry Node**: Starting point for cutscenes (Light Green)
+- **Exit Node**: Ending point for cutscenes (Red)
+- **MoveAction Node**: Moves characters to specific positions (Blue)
+- **TurnAction Node**: Rotates characters to face targets (Green)
+- **DialogueAction Node**: Displays character dialogue (Yellow)
+- **AnimationAction Node**: Plays character animations (Purple)
+- **WaitAction Node**: Creates time delays (Gray)
+- **ParallelGroup Node**: Groups actions for simultaneous execution (Orange)
+
+## Installation
+
+1. Download the plugin package
+2. Extract the contents to your Godot project's `addons` folder
+3. Enable the plugin in Project Settings > Plugins
+4. Restart Godot
+
+## Usage
+
+1. **Creating Nodes**: Right-click on the graph canvas and select a node type
+2. **Connecting Nodes**: Drag from output connection points to input connection points
+3. **Editing Parameters**: Select a node and modify parameters in the Properties Panel
+4. **Previewing**: Click the Preview button to see your cutscene in action
+5. **Saving**: Use File > Save to store your cutscene design
+
+## Requirements
+
+- Godot Engine 4.3 or later
+- OpenGL 3.3 or Vulkan compatible graphics card
+
+## License
+
+This plugin is released under the MIT License. See LICENSE file for details.
+
+## Support
+
+For support, please visit:
+- Documentation: [https://example.com/docs](https://example.com/docs)
+- Issues: [https://github.com/example/cutscene-editor/issues](https://github.com/example/cutscene-editor/issues)
\ No newline at end of file
diff --git a/addons/cutscene_editor/editor/CutsceneGenerator.gd b/addons/cutscene_editor/editor/CutsceneGenerator.gd
new file mode 100644
index 0000000..f189249
--- /dev/null
+++ b/addons/cutscene_editor/editor/CutsceneGenerator.gd
@@ -0,0 +1,312 @@
+@tool
+class_name CutsceneGenerator
+extends Object
+
+# System for generating executable cutscene code from graph data
+
+# Properties
+var graph_nodes: Dictionary = {} # Map of node names to node data
+var connections: Array = [] # List of connection data
+var logical_connections: Array = [] # Logical connections for parallel groups
+var node_instances: Dictionary = {} # Map of node names to instantiated nodes
+
+# Generate a cutscene from graph data
+func generate_cutscene(cutscene_resource: CutsceneResource) -> CutsceneManager:
+ # Clear previous data
+ graph_nodes.clear()
+ connections.clear()
+ logical_connections.clear()
+ node_instances.clear()
+
+ # Load graph data
+ for node_data in cutscene_resource.nodes:
+ graph_nodes[node_data["name"]] = node_data
+
+ connections = cutscene_resource.connections
+
+ # Load logical connections if they exist
+ if cutscene_resource.has_method("get_parallel_connections"):
+ logical_connections = cutscene_resource.get_parallel_connections()
+
+ # Create CutsceneManager
+ var cutscene_manager = CutsceneManager.new()
+
+ # Find entry node
+ var entry_node = _find_entry_node()
+ if not entry_node:
+ printerr("No entry node found in cutscene")
+ return cutscene_manager
+
+ # Build action sequence starting from entry node
+ _build_action_sequence(entry_node, cutscene_manager)
+
+ return cutscene_manager
+
+# Find the entry node in the graph
+func _find_entry_node() -> Dictionary:
+ for node_name in graph_nodes:
+ var node_data = graph_nodes[node_name]
+ if node_data["type"] == "entry":
+ return node_data
+ return {}
+
+# Build action sequence from graph data
+func _build_action_sequence(start_node: Dictionary, cutscene_manager: CutsceneManager) -> void:
+ # Use a queue-based traversal to process nodes in order
+ var node_queue = []
+ var processed_nodes = {}
+ var next_connections = _get_outgoing_connections(start_node["name"])
+
+ # Add initial connections to queue
+ for conn in next_connections:
+ node_queue.append(conn["to_node"])
+
+ # Process nodes in order
+ while not node_queue.is_empty():
+ var current_node_name = node_queue.pop_front()
+
+ # Skip if already processed
+ if processed_nodes.has(current_node_name):
+ continue
+
+ # Mark as processed
+ processed_nodes[current_node_name] = true
+
+ # Get node data
+ var node_data = graph_nodes.get(current_node_name, {})
+ if node_data.is_empty():
+ continue
+
+ # Handle parallel groups specially
+ if node_data["type"] == "parallel":
+ _handle_parallel_group(node_data, cutscene_manager, node_queue)
+ else:
+ # Create action for regular nodes
+ var action = _create_action_from_node(node_data)
+ if action:
+ cutscene_manager.add_action(action)
+
+ # Add next nodes to queue
+ var outgoing_connections = _get_outgoing_connections(current_node_name)
+ for conn in outgoing_connections:
+ if not processed_nodes.has(conn["to_node"]):
+ node_queue.append(conn["to_node"])
+
+# Handle parallel group nodes
+func _handle_parallel_group(parallel_node: Dictionary, cutscene_manager: CutsceneManager, node_queue: Array) -> void:
+ # Find all nodes connected to this parallel group
+ var parallel_actions = []
+
+ # Look for logical connections to this parallel group
+ for conn in logical_connections:
+ if conn["to_node"] == parallel_node["name"] and conn.get("is_parallel", false):
+ var source_node_name = conn["from_node"]
+ var source_node_data = graph_nodes.get(source_node_name, {})
+ if not source_node_data.is_empty():
+ var action = _create_action_from_node(source_node_data)
+ if action:
+ parallel_actions.append(action)
+
+ # Add parallel actions to cutscene manager
+ if not parallel_actions.is_empty():
+ cutscene_manager.add_parallel_actions(parallel_actions)
+
+ # Add nodes connected to parallel group output to queue
+ var outgoing_connections = _get_outgoing_connections(parallel_node["name"])
+ for conn in outgoing_connections:
+ node_queue.append(conn["to_node"])
+
+# Create an action instance from node data
+func _create_action_from_node(node_data: Dictionary):
+ var parameters = node_data["parameters"]
+
+ match node_data["type"]:
+ "move":
+ var character_path = parameters.get("character", "")
+ var target_x = parameters.get("target_x", 0.0)
+ var target_y = parameters.get("target_y", 0.0)
+ var speed = parameters.get("speed", 100.0)
+
+ # In a real implementation, we would resolve the character path to an actual node
+ # For now, we'll create a placeholder
+ var character_node = null # This would be resolved at runtime
+ var target_position = Vector2(target_x, target_y)
+
+ return MoveAction.new(character_node, target_position, speed)
+
+ "turn":
+ var character_path = parameters.get("character", "")
+ var target = parameters.get("target", "")
+ var turn_speed = parameters.get("turn_speed", 2.0)
+
+ # In a real implementation, we would resolve the paths to actual nodes
+ var character_node = null # This would be resolved at runtime
+ var target_node = null # This would be resolved at runtime
+
+ return TurnAction.new(character_node, target_node, turn_speed)
+
+ "dialogue":
+ var character_path = parameters.get("character", "")
+ var text = parameters.get("text", "")
+ var duration = parameters.get("duration", 0.0)
+
+ var character_node = null # This would be resolved at runtime
+
+ return DialogueAction.new(character_node, text, duration)
+
+ "animation":
+ var character_path = parameters.get("character", "")
+ var animation_name = parameters.get("animation_name", "")
+ var loop = parameters.get("loop", false)
+
+ var character_node = null # This would be resolved at runtime
+
+ return AnimationAction.new(character_node, animation_name, loop)
+
+ "wait":
+ var duration = parameters.get("duration", 1.0)
+ return WaitAction.new(duration)
+
+ _:
+ printerr("Unknown node type: %s" % node_data["type"])
+ return null
+
+# Get outgoing connections from a node
+func _get_outgoing_connections(node_name: String) -> Array:
+ var outgoing = []
+ for conn in connections:
+ if conn["from_node"] == node_name:
+ outgoing.append(conn)
+ return outgoing
+
+# Get incoming connections to a node
+func _get_incoming_connections(node_name: String) -> Array:
+ var incoming = []
+ for conn in connections:
+ if conn["to_node"] == node_name:
+ incoming.append(conn)
+ return incoming
+
+# Generate GDScript code from graph data (alternative output format)
+func generate_gdscript_code(cutscene_resource: CutsceneResource) -> String:
+ var code = "# Generated Cutscene Code\n"
+ code += "extends Node2D\n\n"
+
+ # Add character variables
+ code += "# Character nodes\n"
+ # This would need to be determined from the graph data
+ code += "@onready var character1: Node2D = $Character1\n"
+ code += "@onready var character2: Node2D = $Character2\n\n"
+
+ code += "# Cutscene manager\n"
+ code += "var cutscene_manager: CutsceneManager\n\n"
+
+ code += "func _ready() -> void:\n"
+ code += " # Initialize the cutscene system\n"
+ code += " setup_cutscene()\n"
+ code += " \n"
+ code += " # Start the cutscene\n"
+ code += " cutscene_manager.start()\n\n"
+
+ code += "func setup_cutscene() -> void:\n"
+ code += " # Create the cutscene manager\n"
+ code += " cutscene_manager = CutsceneManager.new()\n"
+ code += " add_child(cutscene_manager)\n"
+ code += " \n"
+
+ # Generate action sequence
+ var action_sequence = _generate_action_sequence_code(cutscene_resource)
+ code += action_sequence
+
+ code += "\n"
+ code += "func _on_cutscene_completed() -> void:\n"
+ code += " print(\"Cutscene completed!\")\n"
+
+ return code
+
+# Generate action sequence code
+func _generate_action_sequence_code(cutscene_resource: CutsceneResource) -> String:
+ var code = ""
+
+ # This is a simplified approach - a real implementation would need to
+ # properly traverse the graph and handle parallel groups
+
+ # Find entry node and traverse from there
+ var entry_node = _find_entry_node_in_resource(cutscene_resource)
+ if entry_node.is_empty():
+ return code
+
+ # For demonstration, we'll just generate code for each node in order
+ # A real implementation would need proper graph traversal
+
+ for node_data in cutscene_resource.nodes:
+ if node_data["type"] == "entry" or node_data["type"] == "exit":
+ continue
+
+ var action_code = _generate_action_code(node_data)
+ if not action_code.is_empty():
+ code += " " + action_code + "\n"
+
+ return code
+
+# Find entry node in resource
+func _find_entry_node_in_resource(cutscene_resource: CutsceneResource) -> Dictionary:
+ for node_data in cutscene_resource.nodes:
+ if node_data["type"] == "entry":
+ return node_data
+ return {}
+
+# Generate code for a single action
+func _generate_action_code(node_data: Dictionary) -> String:
+ var parameters = node_data["parameters"]
+
+ match node_data["type"]:
+ "move":
+ var character = parameters.get("character", "character1")
+ var x = parameters.get("target_x", 0.0)
+ var y = parameters.get("target_y", 0.0)
+ var speed = parameters.get("speed", 100.0)
+ return "cutscene_manager.add_action(MoveAction.new(%s, Vector2(%f, %f), %f))" % [character, x, y, speed]
+
+ "turn":
+ var character = parameters.get("character", "character1")
+ var target = parameters.get("target", "character2")
+ var speed = parameters.get("turn_speed", 2.0)
+ return "cutscene_manager.add_action(TurnAction.new(%s, %s, %f))" % [character, target, speed]
+
+ "dialogue":
+ var character = parameters.get("character", "character1")
+ var text = parameters.get("text", "")
+ var duration = parameters.get("duration", 0.0)
+ # Escape quotes in text
+ var escaped_text = text.replace("\"", "\\\"")
+ return "cutscene_manager.add_action(DialogueAction.new(%s, \"%s\", %f))" % [character, escaped_text, duration]
+
+ "animation":
+ var character = parameters.get("character", "character1")
+ var anim_name = parameters.get("animation_name", "")
+ var loop = "true" if parameters.get("loop", false) else "false"
+ return "cutscene_manager.add_action(AnimationAction.new(%s, \"%s\", %s))" % [character, anim_name, loop]
+
+ "wait":
+ var duration = parameters.get("duration", 1.0)
+ return "cutscene_manager.add_action(WaitAction.new(%f))" % duration
+
+ _:
+ return ""
+
+# Export to different formats
+func export_to_format(cutscene_resource: CutsceneResource, format: String) -> String:
+ match format:
+ "gdscript":
+ return generate_gdscript_code(cutscene_resource)
+ "json":
+ return _export_to_json(cutscene_resource)
+ _:
+ return ""
+
+# Export to JSON format
+func _export_to_json(cutscene_resource: CutsceneResource) -> String:
+ # Convert the resource to JSON
+ var json = JSON.new()
+ return json.stringify(cutscene_resource, " ")
diff --git a/addons/cutscene_editor/editor/CutsceneGraphEdit.gd b/addons/cutscene_editor/editor/CutsceneGraphEdit.gd
new file mode 100644
index 0000000..0ac1a7e
--- /dev/null
+++ b/addons/cutscene_editor/editor/CutsceneGraphEdit.gd
@@ -0,0 +1,634 @@
+@tool
+class_name CutsceneGraphEdit
+extends GraphEdit
+
+# Main graph editor interface
+
+# Signals
+signal node_added(node)
+signal node_removed(node)
+signal connection_created(from_node, from_port, to_node, to_port)
+signal connection_removed(from_node, from_port, to_node, to_port)
+signal graph_changed()
+
+# Properties
+var node_counter: int = 0 # For generating unique node IDs
+var current_cutscene: CutsceneResource # The cutscene being edited
+
+# Preview properties
+var preview_manager: PreviewManager
+var preview_panel: PreviewPanel
+
+# Undo/Redo properties
+var undo_redo_manager: UndoRedoManager
+
+# Called when the node is ready
+func _ready() -> void:
+ # Set up GraphEdit properties
+ set_right_disconnects(true)
+ set_show_grid(true)
+ set_snapping_enabled(true)
+ set_snapping_distance(20)
+
+ # Connect to GraphEdit signals
+ connect("connection_request", _on_connection_request)
+ connect("disconnection_request", _on_disconnection_request)
+ connect("node_selected", _on_node_selected)
+ connect("node_unselected", _on_node_unselected)
+ connect("popup_request", _on_popup_request)
+ connect("connection_to_empty", _on_connection_to_empty)
+ connect("connection_from_empty", _on_connection_from_empty)
+
+
+ # Add mini-map
+ _setup_minimap()
+
+
+# Set up mini-map
+func _setup_minimap() -> void:
+ # Enable mini-map
+ set_minimap_enabled(true)
+ set_minimap_size(Vector2(200, 150))
+ #set_minimap_offset(Vector2(10, 10))
+
+# Add a new node to the graph
+func add_node(node_type: String, position: Vector2) -> BaseGraphNode:
+ var new_node: BaseGraphNode
+
+ match node_type:
+ "entry":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/EntryNode.gd").new()
+ "exit":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/ExitNode.gd").new()
+ "move":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/MoveActionNode.gd").new()
+ "turn":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/TurnActionNode.gd").new()
+ "dialogue":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/DialogueActionNode.gd").new()
+ "animation":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/AnimationActionNode.gd").new()
+ "wait":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/WaitActionNode.gd").new()
+ "parallel":
+ new_node = preload("res://addons/cutscene_editor/editor/nodes/ParallelGroupNode.gd").new()
+ _:
+ return null
+
+ # Set node position
+ new_node.position_offset = position
+
+ # Special handling for parallel groups
+ if new_node is ParallelGroupNode:
+ # Set a larger initial size for parallel groups
+ new_node.custom_minimum_size = Vector2(200, 150)
+
+ # Add to GraphEdit
+ add_child(new_node)
+
+ # Connect node signals
+ new_node.connect("node_selected", _on_graph_node_selected)
+ new_node.connect("node_deleted", _on_graph_node_deleted)
+ new_node.connect("parameter_changed", _on_node_parameter_changed)
+
+ # Emit signal
+ emit_signal("node_added", new_node)
+ emit_signal("graph_changed")
+
+ return new_node
+
+# Handle connection requests
+func _on_connection_request(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ # Validate connection before creating it
+ if _is_connection_valid(from_node, from_port, to_node, to_port):
+ # Get node references
+ var from_node_instance = get_node_or_null(from_node)
+ var to_node_instance = get_node_or_null(to_node)
+
+ # Special handling for parallel groups
+ if to_node_instance is ParallelGroupNode:
+ # Connecting to a parallel group
+ if _is_valid_parallel_connection(from_node_instance, from_port, to_node_instance, to_port):
+ # Add child node to parallel group
+ if to_node_instance.can_add_child_node(from_node_instance):
+ to_node_instance.add_child_node(from_node_instance)
+
+ # Create a logical connection (not visual)
+ _create_logical_parallel_connection(from_node, from_port, to_node, to_port)
+
+ # Emit signal
+ emit_signal("connection_created", from_node, from_port, to_node, to_port)
+ emit_signal("graph_changed")
+ return
+
+ # Create the connection
+ connect_node(from_node, from_port, to_node, to_port)
+
+ # Emit signal
+ emit_signal("connection_created", from_node, from_port, to_node, to_port)
+ emit_signal("graph_changed")
+ else:
+ # Show error feedback
+ _show_connection_error(from_node, from_port, to_node, to_port)
+
+# Handle disconnection requests
+func _on_disconnection_request(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ # Get node references
+ var from_node_instance = get_node_or_null(from_node)
+ var to_node_instance = get_node_or_null(to_node)
+
+ # Special handling for parallel groups
+ if to_node_instance is ParallelGroupNode:
+ # Remove child node from parallel group
+ if from_node_instance in to_node_instance.child_nodes:
+ to_node_instance.remove_child_node(from_node_instance)
+
+ # Remove logical connection
+ _remove_logical_parallel_connection(from_node, from_port, to_node, to_port)
+
+ # Emit signal
+ emit_signal("connection_removed", from_node, from_port, to_node, to_port)
+ emit_signal("graph_changed")
+ return
+
+ # Remove the connection
+ disconnect_node(from_node, from_port, to_node, to_port)
+
+ # Emit signal
+ emit_signal("connection_removed", from_node, from_port, to_node, to_port)
+ emit_signal("graph_changed")
+
+# Validate if a connection is valid
+func _is_connection_valid(from_node_name: String, from_port: int, to_node_name: String, to_port: int) -> bool:
+ # Get node references
+ var from_node = get_node_or_null(from_node_name)
+ var to_node = get_node_or_null(to_node_name)
+
+ if not from_node or not to_node:
+ return false
+
+ # Prevent connections to self
+ if from_node == to_node:
+ return false
+
+ # Prevent duplicate connections
+ if is_node_connected(from_node_name, from_port, to_node_name, to_port):
+ return false
+
+ # Prevent circular dependencies
+ if _would_create_cycle(from_node_name, to_node_name):
+ return false
+
+ # Validate connection based on node types
+ match from_node.node_type:
+ "entry":
+ # Entry can connect to any node except entry/exit
+ return to_node.node_type != "entry" and to_node.node_type != "exit"
+ "exit":
+ # Exit cannot have outgoing connections
+ return false
+ "parallel":
+ # Parallel group output can connect to any node except entry/parallel
+ if from_port == from_node.input_connections: # Output port
+ return to_node.node_type != "entry" and to_node.node_type != "parallel"
+ else: # Input port
+ return to_node.node_type != "exit" and to_node.node_type != "parallel"
+ _:
+ # Other nodes can connect to any node except entry
+ return to_node.node_type != "entry"
+
+ return true
+
+# Check if creating a connection would create a cycle
+func _would_create_cycle(from_node_name: String, to_node_name: String) -> bool:
+ # Simple cycle detection using depth-first search
+ var visited = {}
+ var recursion_stack = {}
+
+ # Start from the target node and see if we can reach the source node
+ return _dfs_cycle_check(to_node_name, from_node_name, visited, recursion_stack)
+
+# Depth-first search for cycle detection
+func _dfs_cycle_check(current_node_name: String, target_node_name: String, visited: Dictionary, recursion_stack: Dictionary) -> bool:
+ # Mark current node as visited and add to recursion stack
+ visited[current_node_name] = true
+ recursion_stack[current_node_name] = true
+
+ # Check all outgoing connections
+ for connection in get_connection_list():
+ if connection["from_node"] == current_node_name:
+ var next_node_name = connection["to_node"]
+
+ # If we reached the target node, we found a cycle
+ if next_node_name == target_node_name:
+ return true
+
+ # If next node not visited, recursively check
+ if not visited.has(next_node_name):
+ if _dfs_cycle_check(next_node_name, target_node_name, visited, recursion_stack):
+ return true
+ # If next node is in recursion stack, we found a cycle
+ elif recursion_stack.has(next_node_name):
+ return true
+
+ # Check parallel connections
+ if has_meta("logical_connections"):
+ var logical_connections = get_meta("logical_connections")
+ for connection in logical_connections:
+ if connection["from_node"] == current_node_name:
+ var next_node_name = connection["to_node"]
+
+ # If we reached the target node, we found a cycle
+ if next_node_name == target_node_name:
+ return true
+
+ # If next node not visited, recursively check
+ if not visited.has(next_node_name):
+ if _dfs_cycle_check(next_node_name, target_node_name, visited, recursion_stack):
+ return true
+ # If next node is in recursion stack, we found a cycle
+ elif recursion_stack.has(next_node_name):
+ return true
+
+ # Remove from recursion stack
+ recursion_stack[current_node_name] = false
+ return false
+
+# Show connection error feedback
+func _show_connection_error(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ # Visual feedback for invalid connection
+ # This could be a temporary red highlight or a popup message
+ print("Invalid connection: %s -> %s" % [from_node, to_node])
+
+# Handle popup request (right-click context menu)
+func _on_popup_request(position: Vector2) -> void:
+ # Create context menu for adding nodes
+ var menu = PopupMenu.new()
+ menu.name = "NodeContextMenu"
+
+ # Add node types to menu
+ menu.add_item("Add Entry Node", 0)
+ menu.add_item("Add Exit Node", 1)
+ menu.add_item("Add Move Action", 2)
+ menu.add_item("Add Turn Action", 3)
+ menu.add_item("Add Dialogue Action", 4)
+ menu.add_item("Add Animation Action", 5)
+ menu.add_item("Add Wait Action", 6)
+ menu.add_item("Add Parallel Group", 7)
+
+ # Connect menu signals
+ menu.connect("id_pressed", _on_context_menu_selected.bind(position))
+
+ # Add menu to scene and show it
+ add_child(menu)
+ menu.position = position
+ menu.popup()
+
+# Handle context menu selection
+func _on_context_menu_selected(id: int, position: Vector2) -> void:
+ var node_type: String
+
+ match id:
+ 0:
+ node_type = "entry"
+ 1:
+ node_type = "exit"
+ 2:
+ node_type = "move"
+ 3:
+ node_type = "turn"
+ 4:
+ node_type = "dialogue"
+ 5:
+ node_type = "animation"
+ 6:
+ node_type = "wait"
+ 7:
+ node_type = "parallel"
+ _:
+ return
+
+ # Add the selected node type
+ add_node(node_type, position)
+
+# Handle zoom in
+func _on_zoom_in_pressed() -> void:
+ # Increase zoom level
+ zoom *= 1.2
+ _update_zoom_label()
+
+# Handle zoom out
+func _on_zoom_out_pressed() -> void:
+ # Decrease zoom level
+ zoom /= 1.2
+ _update_zoom_label()
+
+# Update zoom label
+func _update_zoom_label() -> void:
+ var zoom_label = get_node_or_null("ZoomControls/ZoomLabel")
+ if zoom_label:
+ zoom_label.text = "%d%%" % (zoom * 100)
+
+# Handle node selection
+func _on_node_selected(node_name: String) -> void:
+ # Handle GraphEdit's built-in node selection
+ pass
+
+# Handle node unselection
+func _on_node_unselected(node_name: String) -> void:
+ # Handle GraphEdit's built-in node unselection
+ pass
+
+# Handle graph node selection
+func _on_graph_node_selected(node: BaseGraphNode) -> void:
+ # Handle custom node selection
+ pass
+
+# Handle graph node deletion
+func _on_graph_node_deleted(node: BaseGraphNode) -> void:
+ # Special handling for parallel groups
+ if node is ParallelGroupNode:
+ # Remove all child nodes first
+ for child_node in node.child_nodes:
+ # Remove child from scene tree
+ if child_node.get_parent():
+ child_node.get_parent().remove_child(child_node)
+
+ # Add child back to main graph
+ add_child(child_node)
+
+ # Update child position to be near the parallel group
+ child_node.position_offset = node.position_offset + Vector2(50, 50)
+
+ # Clear child nodes array
+ node.child_nodes.clear()
+
+ # Remove all connections to/from this node
+ var connections = get_connection_list()
+ for connection in connections:
+ if connection["from_node"] == node.name or connection["to_node"] == node.name:
+ disconnect_node(connection["from_node"], connection["from_port"], connection["to_node"], connection["to_port"])
+
+ # Remove logical connections for parallel groups
+ if has_meta("logical_connections"):
+ var logical_connections = get_meta("logical_connections")
+ var i = 0
+ while i < logical_connections.size():
+ var conn = logical_connections[i]
+ if conn["from_node"] == node.name or conn["to_node"] == node.name:
+ logical_connections.remove_at(i)
+ else:
+ i += 1
+
+ # Remove node from GraphEdit
+ remove_child(node)
+
+ # Emit signals
+ emit_signal("node_removed", node)
+ emit_signal("graph_changed")
+
+# Handle node parameter changes
+func _on_node_parameter_changed(node: BaseGraphNode, parameter_name: String, new_value) -> void:
+ # Handle parameter changes in nodes
+ emit_signal("graph_changed")
+
+# Handle connection to empty space
+func _on_connection_to_empty(from_node: String, from_port: int, release_position: Vector2) -> void:
+ # This could be used to show a menu or create a new node
+ pass
+
+# Handle connection from empty space
+func _on_connection_from_empty(to_node: String, to_port: int, release_position: Vector2) -> void:
+ # This could be used to show a menu or create a new node
+ pass
+
+# Clear the graph
+func clear_graph() -> void:
+ # Remove all nodes
+ for child in get_children():
+ if child is BaseGraphNode:
+ remove_child(child)
+ child.queue_free()
+
+ # Clear all connections
+ clear_connections()
+
+ # Reset node counter
+ node_counter = 0
+
+ # Emit signal
+ emit_signal("graph_changed")
+
+# Load graph from cutscene resource
+func load_from_cutscene(cutscene: CutsceneResource) -> void:
+ # Clear existing graph
+ clear_graph()
+
+ # Set current cutscene
+ current_cutscene = cutscene
+
+ # Create nodes from cutscene data
+ for node_data in cutscene.nodes:
+ var node = add_node(node_data["type"], Vector2(node_data["x"], node_data["y"]))
+ if node:
+ node.name = node_data["name"]
+ # Set node parameters
+ for param_name in node_data["parameters"]:
+ node.set_parameter(param_name, node_data["parameters"][param_name])
+
+ # Create connections from cutscene data
+ for connection_data in cutscene.connections:
+ connect_node(connection_data["from_node"], connection_data["from_port"],
+ connection_data["to_node"], connection_data["to_port"])
+
+ # Emit signal
+ emit_signal("graph_changed")
+
+# Save graph to cutscene resource
+func save_to_cutscene() -> CutsceneResource:
+ if not current_cutscene:
+ current_cutscene = preload("res://addons/cutscene_editor/editor/resources/CutsceneResource.gd").new()
+
+ # Clear existing data
+ current_cutscene.nodes.clear()
+ current_cutscene.connections.clear()
+
+ # Save nodes
+ for child in get_children():
+ if child is BaseGraphNode:
+ var node_data = {
+ "name": child.name,
+ "type": child.node_type,
+ "x": child.position_offset.x,
+ "y": child.position_offset.y,
+ "parameters": child.action_parameters
+ }
+ current_cutscene.nodes.append(node_data)
+
+ # Save connections
+ for connection in get_connection_list():
+ var connection_data = {
+ "from_node": connection["from_node"],
+ "from_port": connection["from_port"],
+ "to_node": connection["to_node"],
+ "to_port": connection["to_port"]
+ }
+ current_cutscene.connections.append(connection_data)
+
+ return current_cutscene
+
+# Special handling for parallel groups
+func _is_valid_parallel_connection(from_node: BaseGraphNode, from_port: int, to_node: ParallelGroupNode, to_port: int) -> bool:
+ # Can only connect to input ports of parallel groups
+ if to_port >= to_node.input_connections:
+ return false
+
+ # Can't connect parallel group to itself
+ if from_node == to_node:
+ return false
+
+ # Can't connect entry or exit nodes to parallel groups
+ if from_node.node_type == "entry" or from_node.node_type == "exit":
+ return false
+
+ # Can't connect parallel groups to parallel groups
+ if from_node.node_type == "parallel":
+ return false
+
+ return true
+
+# Create a logical connection for parallel groups (not visually represented)
+func _create_logical_parallel_connection(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ # Store the logical connection in our data structure
+ # This connection won't be visually represented but will be used for generation
+ var logical_connection = {
+ "from_node": from_node,
+ "from_port": from_port,
+ "to_node": to_node,
+ "to_port": to_port,
+ "is_parallel": true
+ }
+
+ # Add to a separate list of logical connections
+ if not has_meta("logical_connections"):
+ set_meta("logical_connections", [])
+
+ var logical_connections = get_meta("logical_connections")
+ logical_connections.append(logical_connection)
+
+# Remove a logical connection for parallel groups
+func _remove_logical_parallel_connection(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ if not has_meta("logical_connections"):
+ return
+
+ var logical_connections = get_meta("logical_connections")
+ var i = 0
+ while i < logical_connections.size():
+ var conn = logical_connections[i]
+ if (conn["from_node"] == from_node and conn["from_port"] == from_port and
+ conn["to_node"] == to_node and conn["to_port"] == to_port):
+ logical_connections.remove_at(i)
+ return
+ i += 1
+
+# Get all logical connections
+func get_logical_connections() -> Array:
+ if has_meta("logical_connections"):
+ return get_meta("logical_connections")
+ return []
+
+# Set up preview system
+func setup_preview() -> void:
+ # Create preview manager
+ preview_manager = PreviewManager.new()
+ add_child(preview_manager)
+
+ # Create preview panel
+ preview_panel = PreviewPanel.new()
+ preview_panel.set_preview_manager(preview_manager)
+ preview_panel.set_graph_edit(self)
+
+ # Add to editor interface (this would be added to the editor UI)
+ # For now, we'll just keep a reference to it
+
+# Start preview
+func start_preview() -> void:
+ if preview_manager:
+ preview_manager.start_preview()
+
+# Stop preview
+func stop_preview() -> void:
+ if preview_manager:
+ preview_manager.stop_preview()
+
+# Pause preview
+func pause_preview() -> void:
+ if preview_manager:
+ preview_manager.pause_preview()
+
+# Resume preview
+func resume_preview() -> void:
+ if preview_manager:
+ preview_manager.resume_preview()
+
+# Set up undo/redo system
+func _setup_undo_redo() -> void:
+ # Create undo/redo manager
+ undo_redo_manager = UndoRedoManager.new()
+ undo_redo_manager.connect("undo_redo_state_changed", _on_undo_redo_state_changed)
+
+ # Connect to existing signals
+ connect("node_added", _on_node_added_for_undo)
+ connect("node_removed", _on_node_removed_for_undo)
+ connect("connection_created", _on_connection_created_for_undo)
+ connect("connection_removed", _on_connection_removed_for_undo)
+ connect("graph_changed", _on_graph_changed_for_undo)
+
+# Handle node added for undo
+func _on_node_added_for_undo(node: BaseGraphNode) -> void:
+ if undo_redo_manager:
+ var operation = undo_redo_manager.create_node_added_operation(node)
+ undo_redo_manager.add_operation(operation)
+
+# Handle node removed for undo
+func _on_node_removed_for_undo(node: BaseGraphNode) -> void:
+ if undo_redo_manager:
+ var operation = undo_redo_manager.create_node_removed_operation(node)
+ undo_redo_manager.add_operation(operation)
+
+# Handle connection created for undo
+func _on_connection_created_for_undo(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ if undo_redo_manager:
+ var operation = undo_redo_manager.create_connection_created_operation(from_node, from_port, to_node, to_port)
+ undo_redo_manager.add_operation(operation)
+
+# Handle connection removed for undo
+func _on_connection_removed_for_undo(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ if undo_redo_manager:
+ var operation = undo_redo_manager.create_connection_removed_operation(from_node, from_port, to_node, to_port)
+ undo_redo_manager.add_operation(operation)
+
+# Handle graph changed for undo
+func _on_graph_changed_for_undo() -> void:
+ # This could be used for batch operations or complex changes
+ pass
+
+# Handle undo/redo state changed
+func _on_undo_redo_state_changed() -> void:
+ # Update UI to reflect undo/redo availability
+ _update_undo_redo_ui()
+
+# Update undo/redo UI
+func _update_undo_redo_ui() -> void:
+ # This would update toolbar buttons or menu items
+ pass
+
+# Perform undo operation
+func undo() -> void:
+ if undo_redo_manager and undo_redo_manager.can_undo():
+ undo_redo_manager.undo()
+
+# Perform redo operation
+func redo() -> void:
+ if undo_redo_manager and undo_redo_manager.can_redo():
+ undo_redo_manager.redo()
diff --git a/addons/cutscene_editor/editor/PreviewManager.gd b/addons/cutscene_editor/editor/PreviewManager.gd
new file mode 100644
index 0000000..13700bc
--- /dev/null
+++ b/addons/cutscene_editor/editor/PreviewManager.gd
@@ -0,0 +1,274 @@
+@tool
+class_name PreviewManager
+extends Node
+
+# Manager for real-time cutscene preview
+
+# Signals
+signal preview_started()
+signal preview_stopped()
+signal preview_paused()
+signal preview_resumed()
+signal preview_progress(progress: float)
+signal node_activated(node_name: String)
+signal node_completed(node_name: String)
+
+# Properties
+var is_previewing: bool = false
+var is_paused: bool = false
+var current_cutscene: CutsceneManager
+var preview_scene: Node2D # The scene being previewed
+var preview_characters: Dictionary = {} # Map of character names to nodes
+var graph_edit: CutsceneGraphEdit # Reference to the graph editor
+
+# Preview settings
+var preview_speed: float = 1.0
+var show_debug_info: bool = true
+
+# Initialize the preview manager
+func _init() -> void:
+ name = "PreviewManager"
+
+# Set up the preview with a graph editor
+func setup_preview(graph: CutsceneGraphEdit) -> void:
+ graph_edit = graph
+
+ # Connect to graph change signals
+ if graph_edit:
+ graph_edit.connect("graph_changed", _on_graph_changed)
+
+# Start the preview
+func start_preview() -> void:
+ if is_previewing:
+ return
+
+ # Create preview scene
+ _setup_preview_scene()
+
+ # Generate cutscene from current graph
+ var cutscene_resource = graph_edit.save_to_cutscene()
+ var generator = CutsceneGenerator.new()
+ current_cutscene = generator.generate_cutscene(cutscene_resource)
+
+ # Add cutscene to preview scene
+ if preview_scene and current_cutscene:
+ preview_scene.add_child(current_cutscene)
+
+ # Connect to cutscene signals for feedback
+ _connect_cutscene_signals()
+
+ # Start the cutscene
+ current_cutscene.start()
+
+ # Update state
+ is_previewing = true
+ is_paused = false
+ emit_signal("preview_started")
+
+# Stop the preview
+func stop_preview() -> void:
+ if not is_previewing:
+ return
+
+ # Stop the cutscene
+ if current_cutscene:
+ current_cutscene.stop()
+ _disconnect_cutscene_signals()
+
+ # Remove from preview scene
+ if current_cutscene.get_parent() == preview_scene:
+ preview_scene.remove_child(current_cutscene)
+ current_cutscene.queue_free()
+
+ # Clean up preview scene
+ _cleanup_preview_scene()
+
+ # Update state
+ is_previewing = false
+ is_paused = false
+ emit_signal("preview_stopped")
+
+# Pause the preview
+func pause_preview() -> void:
+ if not is_previewing or is_paused:
+ return
+
+ if current_cutscene:
+ current_cutscene.pause()
+ is_paused = true
+ emit_signal("preview_paused")
+
+# Resume the preview
+func resume_preview() -> void:
+ if not is_previewing or not is_paused:
+ return
+
+ if current_cutscene:
+ current_cutscene.resume()
+ is_paused = false
+ emit_signal("preview_resumed")
+
+# Set preview speed
+func set_preview_speed(speed: float) -> void:
+ preview_speed = speed
+ # In a real implementation, this would affect the time scale
+ # of the preview scene
+
+# Set up the preview scene
+func _setup_preview_scene() -> void:
+ # Create or reuse preview scene
+ if not preview_scene:
+ preview_scene = Node2D.new()
+ preview_scene.name = "PreviewScene"
+ add_child(preview_scene)
+
+ # Set up characters for preview
+ _setup_preview_characters()
+
+# Set up characters for preview
+func _setup_preview_characters() -> void:
+ # Clear existing characters
+ preview_characters.clear()
+
+ # Create placeholder characters for preview
+ # In a real implementation, this would load actual character scenes
+ var character1 = _create_preview_character("Character1", Vector2(100, 100))
+ var character2 = _create_preview_character("Character2", Vector2(200, 100))
+
+ preview_characters["Character1"] = character1
+ preview_characters["Character2"] = character2
+
+ # Add to preview scene
+ if preview_scene:
+ preview_scene.add_child(character1)
+ preview_scene.add_child(character2)
+
+# Create a preview character
+func _create_preview_character(name: String, position: Vector2) -> Node2D:
+ var character = Node2D.new()
+ character.name = name
+ character.position = position
+
+ # Add a visual representation
+ var sprite = Polygon2D.new()
+ sprite.polygon = PackedVector2Array([
+ Vector2(-10, -20),
+ Vector2(-30, 0),
+ Vector2(-10, 20),
+ Vector2(10, 20),
+ Vector2(30, 0),
+ Vector2(10, -20)
+ ])
+ sprite.color = Color(0.5, 0.7, 1.0)
+ character.add_child(sprite)
+
+ return character
+
+# Clean up the preview scene
+func _cleanup_preview_scene() -> void:
+ # Remove characters
+ for char_name in preview_characters:
+ var character = preview_characters[char_name]
+ if character.get_parent() == preview_scene:
+ preview_scene.remove_child(character)
+ character.queue_free()
+
+ preview_characters.clear()
+
+# Connect to cutscene signals for feedback
+func _connect_cutscene_signals() -> void:
+ if not current_cutscene:
+ return
+
+ # Disconnect existing connections
+ _disconnect_cutscene_signals()
+
+ # Connect to signals
+ current_cutscene.connect("cutscene_started", _on_cutscene_started)
+ current_cutscene.connect("cutscene_completed", _on_cutscene_completed)
+ current_cutscene.connect("cutscene_paused", _on_cutscene_paused)
+ current_cutscene.connect("cutscene_resumed", _on_cutscene_resumed)
+ current_cutscene.connect("action_started", _on_action_started)
+ current_cutscene.connect("action_completed", _on_action_completed)
+
+# Disconnect from cutscene signals
+func _disconnect_cutscene_signals() -> void:
+ if not current_cutscene:
+ return
+
+ if current_cutscene.is_connected("cutscene_started", _on_cutscene_started):
+ current_cutscene.disconnect("cutscene_started", _on_cutscene_started)
+ if current_cutscene.is_connected("cutscene_completed", _on_cutscene_completed):
+ current_cutscene.disconnect("cutscene_completed", _on_cutscene_completed)
+ if current_cutscene.is_connected("cutscene_paused", _on_cutscene_paused):
+ current_cutscene.disconnect("cutscene_paused", _on_cutscene_paused)
+ if current_cutscene.is_connected("cutscene_resumed", _on_cutscene_resumed):
+ current_cutscene.disconnect("cutscene_resumed", _on_cutscene_resumed)
+ if current_cutscene.is_connected("action_started", _on_action_started):
+ current_cutscene.disconnect("action_started", _on_action_started)
+ if current_cutscene.is_connected("action_completed", _on_action_completed):
+ current_cutscene.disconnect("action_completed", _on_action_completed)
+
+# Handle graph changes
+func _on_graph_changed() -> void:
+ # If we're previewing, restart the preview to reflect changes
+ if is_previewing:
+ stop_preview()
+ start_preview()
+
+# Handle cutscene started
+func _on_cutscene_started() -> void:
+ emit_signal("preview_started")
+
+# Handle cutscene completed
+func _on_cutscene_completed() -> void:
+ emit_signal("preview_stopped")
+ is_previewing = false
+ is_paused = false
+
+# Handle cutscene paused
+func _on_cutscene_paused() -> void:
+ emit_signal("preview_paused")
+ is_paused = true
+
+# Handle cutscene resumed
+func _on_cutscene_resumed() -> void:
+ emit_signal("preview_resumed")
+ is_paused = false
+
+# Handle action started
+func _on_action_started(action: Action) -> void:
+ # Find the corresponding node in the graph and highlight it
+ var node_name = _find_node_for_action(action)
+ if node_name:
+ emit_signal("node_activated", node_name)
+
+ # Update graph editor visualization
+ if graph_edit:
+ var node = graph_edit.get_node_or_null(node_name)
+ if node and node.has_method("set_state"):
+ node.set_state(BaseGraphNode.NodeState.ACTIVE)
+
+# Handle action completed
+func _on_action_completed(action: Action) -> void:
+ # Find the corresponding node in the graph and mark as completed
+ var node_name = _find_node_for_action(action)
+ if node_name:
+ emit_signal("node_completed", node_name)
+
+ # Update graph editor visualization
+ if graph_edit:
+ var node = graph_edit.get_node_or_null(node_name)
+ if node and node.has_method("set_state"):
+ node.set_state(BaseGraphNode.NodeState.COMPLETED)
+
+# Find node corresponding to an action
+func _find_node_for_action(action: Action) -> String:
+ # This would need to map actions to nodes
+ # In a real implementation, we would store this mapping when generating the cutscene
+ # For now, we'll return a placeholder
+ return ""
+
+# Get the preview scene for display
+func get_preview_scene() -> Node2D:
+ return preview_scene
\ No newline at end of file
diff --git a/addons/cutscene_editor/editor/PreviewPanel.gd b/addons/cutscene_editor/editor/PreviewPanel.gd
new file mode 100644
index 0000000..2cfb9e0
--- /dev/null
+++ b/addons/cutscene_editor/editor/PreviewPanel.gd
@@ -0,0 +1,186 @@
+@tool
+class_name PreviewPanel
+extends PanelContainer
+
+# UI panel for displaying the preview
+
+# UI elements
+var preview_viewport: SubViewport
+var preview_texture: TextureRect
+var controls_container: HBoxContainer
+var play_button: Button
+var pause_button: Button
+var stop_button: Button
+var speed_slider: HSlider
+var progress_bar: ProgressBar
+var debug_label: Label
+
+# Properties
+var preview_manager: PreviewManager
+var is_playing: bool = false
+
+# Initialize the preview panel
+func _ready() -> void:
+ _setup_ui()
+ _setup_signals()
+
+# Set up the UI
+func _setup_ui() -> void:
+ # Main container
+ var main_vbox = VBoxContainer.new()
+ main_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ main_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ add_child(main_vbox)
+
+ # Preview viewport
+ var viewport_container = AspectRatioContainer.new()
+ viewport_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ viewport_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ viewport_container.ratio = 16.0/9.0
+ main_vbox.add_child(viewport_container)
+
+ preview_viewport = SubViewport.new()
+ preview_viewport.size = Vector2i(800, 450)
+ preview_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
+ viewport_container.add_child(preview_viewport)
+
+ preview_texture = TextureRect.new()
+ preview_texture.expand = true
+ preview_texture.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ preview_texture.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ preview_texture.texture = preview_viewport.get_texture()
+ viewport_container.add_child(preview_texture)
+
+ # Controls
+ controls_container = HBoxContainer.new()
+ controls_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ main_vbox.add_child(controls_container)
+
+ play_button = Button.new()
+ play_button.text = "▶"
+ play_button.flat = true
+ controls_container.add_child(play_button)
+
+ pause_button = Button.new()
+ pause_button.text = "⏸"
+ pause_button.flat = true
+ controls_container.add_child(pause_button)
+
+ stop_button = Button.new()
+ stop_button.text = "⏹"
+ stop_button.flat = true
+ controls_container.add_child(stop_button)
+
+ var speed_label = Label.new()
+ speed_label.text = "Speed:"
+ controls_container.add_child(speed_label)
+
+ speed_slider = HSlider.new()
+ speed_slider.min = 0.1
+ speed_slider.max = 2.0
+ speed_slider.step = 0.1
+ speed_slider.value = 1.0
+ speed_slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ controls_container.add_child(speed_slider)
+
+ # Progress bar
+ progress_bar = ProgressBar.new()
+ progress_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ progress_bar.percent_visible = true
+ main_vbox.add_child(progress_bar)
+
+ # Debug info
+ debug_label = Label.new()
+ debug_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ main_vbox.add_child(debug_label)
+
+# Set up signal connections
+func _setup_signals() -> void:
+ play_button.connect("pressed", _on_play_pressed)
+ pause_button.connect("pressed", _on_pause_pressed)
+ stop_button.connect("pressed", _on_stop_pressed)
+ speed_slider.connect("value_changed", _on_speed_changed)
+
+# Set the preview manager
+func set_preview_manager(manager: PreviewManager) -> void:
+ preview_manager = manager
+
+ # Connect to preview manager signals
+ if preview_manager:
+ preview_manager.connect("preview_started", _on_preview_started)
+ preview_manager.connect("preview_stopped", _on_preview_stopped)
+ preview_manager.connect("preview_paused", _on_preview_paused)
+ preview_manager.connect("preview_resumed", _on_preview_resumed)
+ preview_manager.connect("preview_progress", _on_preview_progress)
+ preview_manager.connect("node_activated", _on_node_activated)
+ preview_manager.connect("node_completed", _on_node_completed)
+
+# Set the graph editor
+func set_graph_edit(graph_edit: CutsceneGraphEdit) -> void:
+ if preview_manager:
+ preview_manager.setup_preview(graph_edit)
+
+ # Add preview scene to viewport
+ var preview_scene = preview_manager.get_preview_scene()
+ if preview_scene and preview_scene.get_parent() != preview_viewport:
+ preview_viewport.add_child(preview_scene)
+
+# Handle play button press
+func _on_play_pressed() -> void:
+ if preview_manager:
+ if is_playing:
+ preview_manager.resume_preview()
+ else:
+ preview_manager.start_preview()
+
+# Handle pause button press
+func _on_pause_pressed() -> void:
+ if preview_manager:
+ preview_manager.pause_preview()
+
+# Handle stop button press
+func _on_stop_pressed() -> void:
+ if preview_manager:
+ preview_manager.stop_preview()
+
+# Handle speed slider change
+func _on_speed_changed(value: float) -> void:
+ if preview_manager:
+ preview_manager.set_preview_speed(value)
+
+# Handle preview started
+func _on_preview_started() -> void:
+ is_playing = true
+ play_button.text = "⏸"
+ debug_label.text = "Preview started"
+
+# Handle preview stopped
+func _on_preview_stopped() -> void:
+ is_playing = false
+ play_button.text = "▶"
+ progress_bar.value = 0
+ debug_label.text = "Preview stopped"
+
+# Handle preview paused
+func _on_preview_paused() -> void:
+ is_playing = false
+ play_button.text = "▶"
+ debug_label.text = "Preview paused"
+
+# Handle preview resumed
+func _on_preview_resumed() -> void:
+ is_playing = true
+ play_button.text = "⏸"
+ debug_label.text = "Preview resumed"
+
+# Handle preview progress
+func _on_preview_progress(progress: float) -> void:
+ progress_bar.value = progress * 100
+
+# Handle node activated
+func _on_node_activated(node_name: String) -> void:
+ debug_label.text = "Executing: %s" % node_name
+
+# Handle node completed
+func _on_node_completed(node_name: String) -> void:
+ debug_label.text = "Completed: %s" % node_name
\ No newline at end of file
diff --git a/addons/cutscene_editor/editor/UndoRedoManager.gd b/addons/cutscene_editor/editor/UndoRedoManager.gd
new file mode 100644
index 0000000..70f7bae
--- /dev/null
+++ b/addons/cutscene_editor/editor/UndoRedoManager.gd
@@ -0,0 +1,246 @@
+@tool
+class_name UndoRedoManager
+extends Object
+
+# Manager for undo/redo operations
+
+# Signals
+signal undo_redo_state_changed()
+
+# Properties
+var undo_stack: Array = [] # Stack of undo operations
+var redo_stack: Array = [] # Stack of redo operations
+var max_history: int = 100 # Maximum number of operations to store
+var is_processing: bool = false # Flag to prevent recursive processing
+
+# Operation types
+enum OperationType {
+ NODE_ADDED,
+ NODE_REMOVED,
+ NODE_MOVED,
+ NODE_PROPERTY_CHANGED,
+ CONNECTION_CREATED,
+ CONNECTION_REMOVED,
+ GRAPH_CLEARED
+}
+
+# Initialize the undo/redo manager
+func _init() -> void:
+ pass
+
+# Add an operation to the undo stack
+func add_operation(operation: Dictionary) -> void:
+ if is_processing:
+ return
+
+ # Add to undo stack
+ undo_stack.push_back(operation)
+
+ # Limit stack size
+ if undo_stack.size() > max_history:
+ undo_stack.pop_front()
+
+ # Clear redo stack when new operation is added
+ redo_stack.clear()
+
+ # Emit signal
+ emit_signal("undo_redo_state_changed")
+
+# Perform undo operation
+func undo() -> void:
+ if undo_stack.is_empty():
+ return
+
+ # Set processing flag to prevent recursive calls
+ is_processing = true
+
+ # Get last operation
+ var operation = undo_stack.pop_back()
+
+ # Perform reverse operation
+ _perform_reverse_operation(operation)
+
+ # Add to redo stack
+ redo_stack.push_back(operation)
+
+ # Limit redo stack size
+ if redo_stack.size() > max_history:
+ redo_stack.pop_front()
+
+ # Clear processing flag
+ is_processing = false
+
+ # Emit signal
+ emit_signal("undo_redo_state_changed")
+
+# Perform redo operation
+func redo() -> void:
+ if redo_stack.is_empty():
+ return
+
+ # Set processing flag to prevent recursive calls
+ is_processing = true
+
+ # Get last redo operation
+ var operation = redo_stack.pop_back()
+
+ # Perform original operation
+ _perform_operation(operation)
+
+ # Add to undo stack
+ undo_stack.push_back(operation)
+
+ # Limit undo stack size
+ if undo_stack.size() > max_history:
+ undo_stack.pop_front()
+
+ # Clear processing flag
+ is_processing = false
+
+ # Emit signal
+ emit_signal("undo_redo_state_changed")
+
+# Check if undo is possible
+func can_undo() -> bool:
+ return not undo_stack.is_empty()
+
+# Check if redo is possible
+func can_redo() -> bool:
+ return not redo_stack.is_empty()
+
+# Clear all history
+func clear_history() -> void:
+ undo_stack.clear()
+ redo_stack.clear()
+ emit_signal("undo_redo_state_changed")
+
+# Perform an operation
+func _perform_operation(operation: Dictionary) -> void:
+ # This would be implemented in the graph editor
+ pass
+
+# Perform the reverse of an operation
+func _perform_reverse_operation(operation: Dictionary) -> void:
+ match operation["type"]:
+ OperationType.NODE_ADDED:
+ _reverse_node_added(operation)
+ OperationType.NODE_REMOVED:
+ _reverse_node_removed(operation)
+ OperationType.NODE_MOVED:
+ _reverse_node_moved(operation)
+ OperationType.NODE_PROPERTY_CHANGED:
+ _reverse_node_property_changed(operation)
+ OperationType.CONNECTION_CREATED:
+ _reverse_connection_created(operation)
+ OperationType.CONNECTION_REMOVED:
+ _reverse_connection_removed(operation)
+ OperationType.GRAPH_CLEARED:
+ _reverse_graph_cleared(operation)
+
+# Create operation for node added
+func create_node_added_operation(node: BaseGraphNode) -> Dictionary:
+ return {
+ "type": OperationType.NODE_ADDED,
+ "node_name": node.name,
+ "node_type": node.node_type,
+ "position": node.position_offset,
+ "parameters": node.action_parameters.duplicate(true)
+ }
+
+# Reverse node added operation
+func _reverse_node_added(operation: Dictionary) -> void:
+ # This would remove the node in the graph editor
+ pass
+
+# Create operation for node removed
+func create_node_removed_operation(node: BaseGraphNode) -> Dictionary:
+ return {
+ "type": OperationType.NODE_REMOVED,
+ "node_name": node.name,
+ "node_type": node.node_type,
+ "position": node.position_offset,
+ "parameters": node.action_parameters.duplicate(true),
+ "connections": _get_node_connections(node.name)
+ }
+
+# Reverse node removed operation
+func _reverse_node_removed(operation: Dictionary) -> void:
+ # This would add the node back in the graph editor
+ pass
+
+# Create operation for node moved
+func create_node_moved_operation(node_name: String, old_position: Vector2, new_position: Vector2) -> Dictionary:
+ return {
+ "type": OperationType.NODE_MOVED,
+ "node_name": node_name,
+ "old_position": old_position,
+ "new_position": new_position
+ }
+
+# Reverse node moved operation
+func _reverse_node_moved(operation: Dictionary) -> void:
+ # This would move the node back to its old position in the graph editor
+ pass
+
+# Create operation for node property changed
+func create_node_property_changed_operation(node_name: String, property_name: String, old_value, new_value) -> Dictionary:
+ return {
+ "type": OperationType.NODE_PROPERTY_CHANGED,
+ "node_name": node_name,
+ "property_name": property_name,
+ "old_value": old_value,
+ "new_value": new_value
+ }
+
+# Reverse node property changed operation
+func _reverse_node_property_changed(operation: Dictionary) -> void:
+ # This would restore the old property value in the graph editor
+ pass
+
+# Create operation for connection created
+func create_connection_created_operation(from_node: String, from_port: int, to_node: String, to_port: int) -> Dictionary:
+ return {
+ "type": OperationType.CONNECTION_CREATED,
+ "from_node": from_node,
+ "from_port": from_port,
+ "to_node": to_node,
+ "to_port": to_port
+ }
+
+# Reverse connection created operation
+func _reverse_connection_created(operation: Dictionary) -> void:
+ # This would remove the connection in the graph editor
+ pass
+
+# Create operation for connection removed
+func create_connection_removed_operation(from_node: String, from_port: int, to_node: String, to_port: int) -> Dictionary:
+ return {
+ "type": OperationType.CONNECTION_REMOVED,
+ "from_node": from_node,
+ "from_port": from_port,
+ "to_node": to_node,
+ "to_port": to_port
+ }
+
+# Reverse connection removed operation
+func _reverse_connection_removed(operation: Dictionary) -> void:
+ # This would recreate the connection in the graph editor
+ pass
+
+# Create operation for graph cleared
+func create_graph_cleared_operation(nodes: Array, connections: Array) -> Dictionary:
+ return {
+ "type": OperationType.GRAPH_CLEARED,
+ "nodes": nodes.duplicate(true),
+ "connections": connections.duplicate(true)
+ }
+
+# Reverse graph cleared operation
+func _reverse_graph_cleared(operation: Dictionary) -> void:
+ # This would restore all nodes and connections in the graph editor
+ pass
+
+# Get connections for a node
+func _get_node_connections(node_name: String) -> Array:
+ # This would retrieve all connections to/from the node
+ return []
\ No newline at end of file
diff --git a/addons/cutscene_editor/editor/nodes/AnimationActionNode.gd b/addons/cutscene_editor/editor/nodes/AnimationActionNode.gd
new file mode 100644
index 0000000..8b90ac0
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/AnimationActionNode.gd
@@ -0,0 +1,62 @@
+@tool
+class_name AnimationActionNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Node for AnimationAction
+
+func _init() -> void:
+ node_type = "animation"
+ node_id = "animation_" + str(randi())
+ title = "Animation"
+ modulate = Color(0.8, 0.4, 0.8) # Purple
+
+ # One input and one output connection
+ var slot = 0
+ set_slot(slot, true, 0, Color(0, 0, 0), true, 0, Color(0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Initialize default parameters
+ action_parameters["character"] = ""
+ action_parameters["animation_name"] = ""
+ action_parameters["loop"] = false
+
+func _setup_parameter_fields() -> void:
+ # Character field
+ var char_label = Label.new()
+ char_label.text = "Character:"
+ add_child(char_label)
+
+ var char_field = LineEdit.new()
+ char_field.text = action_parameters["character"]
+ char_field.connect("text_changed", _on_character_changed)
+ add_child(char_field)
+
+ # Animation name field
+ var anim_label = Label.new()
+ anim_label.text = "Animation:"
+ add_child(anim_label)
+
+ var anim_field = LineEdit.new()
+ anim_field.text = action_parameters["animation_name"]
+ anim_field.connect("text_changed", _on_animation_changed)
+ add_child(anim_field)
+
+ # Loop checkbox
+ var loop_label = Label.new()
+ loop_label.text = "Loop:"
+ add_child(loop_label)
+
+ var loop_checkbox = CheckBox.new()
+ loop_checkbox.button_pressed = action_parameters["loop"]
+ loop_checkbox.connect("toggled", _on_loop_toggled)
+ add_child(loop_checkbox)
+
+func _on_character_changed(new_text: String) -> void:
+ set_parameter("character", new_text)
+
+func _on_animation_changed(new_text: String) -> void:
+ set_parameter("animation_name", new_text)
+
+func _on_loop_toggled(button_pressed: bool) -> void:
+ set_parameter("loop", button_pressed)
diff --git a/addons/cutscene_editor/editor/nodes/BaseGraphNode.gd b/addons/cutscene_editor/editor/nodes/BaseGraphNode.gd
new file mode 100644
index 0000000..f70d925
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/BaseGraphNode.gd
@@ -0,0 +1,284 @@
+@tool
+class_name BaseGraphNode
+extends GraphNode
+
+# Base class for all cutscene graph nodes
+
+# Signals
+signal node_selected2(node)
+signal node_deleted(node)
+signal parameter_changed(node, parameter_name, new_value)
+
+# Node states
+enum NodeState {
+ IDLE,
+ ACTIVE,
+ COMPLETED,
+ ERROR,
+ PAUSED
+}
+
+# Properties
+var node_type: String = "base"
+var node_id: String # Unique identifier for the node
+var action_parameters: Dictionary = {} # Stores parameter values
+var current_state: int = NodeState.IDLE
+var error_message: String = ""
+var property_editors: Dictionary = {} # Map of property names to editors
+
+# Visual feedback elements
+var state_border: ColorRect
+var overlay_icon: TextureRect
+var checkmark_texture: Texture2D
+var error_texture: Texture2D
+var pause_texture: Texture2D
+
+# Called when the node is ready
+func _ready() -> void:
+ # Set up common node properties
+ connect("dragged", _on_node_dragged)
+ connect("selected", _on_node_selected)
+
+ # Add close butto
+
+ # Set up visual feedback elements
+ _setup_visual_feedback()
+
+ # Add parameter fields based on node type
+ _setup_parameter_fields()
+
+# Set up visual feedback elements
+func _setup_visual_feedback() -> void:
+ # Create state border
+ state_border = ColorRect.new()
+ state_border.name = "StateBorder"
+ state_border.anchor_right = 1
+ state_border.anchor_bottom = 1
+ state_border.margin_left = -2
+ state_border.margin_top = -2
+ state_border.margin_right = 2
+ state_border.margin_bottom = 2
+ state_border.color = Color(0, 0, 0, 0) # Transparent by default
+ state_border.z_index = -1 # Behind the node
+ add_child(state_border)
+
+ # Create overlay icon container
+ var overlay_container = CenterContainer.new()
+ overlay_container.name = "OverlayContainer"
+ overlay_container.anchor_right = 1
+ overlay_container.anchor_bottom = 1
+ overlay_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ add_child(overlay_container)
+
+ # Create overlay icon
+ overlay_icon = TextureRect.new()
+ overlay_icon.name = "OverlayIcon"
+ overlay_icon.expand = true
+ overlay_icon.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
+ overlay_icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER
+ overlay_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ overlay_container.add_child(overlay_icon)
+
+ # Load textures (these would be actual textures in a real implementation)
+ # checkmark_texture = preload("res://addons/cutscene_editor/icons/checkmark.png")
+ # error_texture = preload("res://addons/cutscene_editor/icons/error.png")
+ # pause_texture = preload("res://addons/cutscene_editor/icons/pause.png")
+
+# Set up parameter fields based on node type
+func _setup_parameter_fields() -> void:
+ # This method should be overridden by subclasses
+ pass
+
+# Set node state and update visual feedback
+func set_state(new_state: int, error_msg: String = "") -> void:
+ current_state = new_state
+ error_message = error_msg
+ _update_visual_feedback()
+
+# Update visual feedback based on current state
+func _update_visual_feedback() -> void:
+ match current_state:
+ NodeState.IDLE:
+ _set_idle_state()
+ NodeState.ACTIVE:
+ _set_active_state()
+ NodeState.COMPLETED:
+ _set_completed_state()
+ NodeState.ERROR:
+ _set_error_state()
+ NodeState.PAUSED:
+ _set_paused_state()
+
+# Set idle state visuals
+func _set_idle_state() -> void:
+ # Reset to default appearance
+ state_border.color = Color(0, 0, 0, 0)
+ overlay_icon.texture = null
+ modulate = Color(1, 1, 1, 1)
+ scale = Vector2(1, 1)
+
+ # Stop any animations
+ _stop_animations()
+
+# Set active state visuals
+func _set_active_state() -> void:
+ # White border highlight
+ state_border.color = Color(1, 1, 1, 1)
+ state_border.size = Vector2(2, 2)
+
+ # Clear overlay
+ overlay_icon.texture = null
+
+ # Start pulsing animation
+ _start_pulsing_animation()
+
+ # Slight scale increase
+ scale = Vector2(1.05, 1.05)
+
+# Set completed state visuals
+func _set_completed_state() -> void:
+ # Green border
+ state_border.color = Color(0, 1, 0, 0.5)
+ state_border.size = Vector2(2, 2)
+
+ # Checkmark overlay
+ overlay_icon.texture = checkmark_texture
+ overlay_icon.modulate = Color(0, 1, 0, 0.7)
+
+ # Slight transparency
+ modulate = Color(1, 1, 1, 0.8)
+
+ # Stop animations
+ _stop_animations()
+
+# Set error state visuals
+func _set_error_state() -> void:
+ # Red border
+ state_border.color = Color(1, 0, 0, 1)
+ state_border.size = Vector2(3, 3)
+
+ # Error icon overlay
+ overlay_icon.texture = error_texture
+ overlay_icon.modulate = Color(1, 0, 0, 1)
+
+ # Red tint
+ modulate = Color(1, 0.7, 0.7, 1)
+
+ # Start shake animation
+ _start_shake_animation()
+
+# Set paused state visuals
+func _set_paused_state() -> void:
+ # Yellow border
+ state_border.color = Color(1, 1, 0, 1)
+ state_border.size = Vector2(2, 2)
+
+ # Pause icon overlay
+ overlay_icon.texture = pause_texture
+ overlay_icon.modulate = Color(1, 1, 0, 1)
+
+ # Stop animations
+ _stop_animations()
+
+# Animation functions
+func _start_pulsing_animation() -> void:
+ # Create animation player for pulsing effect
+ var anim_player = AnimationPlayer.new()
+ anim_player.name = "StateAnimationPlayer"
+
+ # Remove existing animation player if present
+ var existing_player = get_node_or_null("StateAnimationPlayer")
+ if existing_player:
+ remove_child(existing_player)
+ existing_player.queue_free()
+
+ add_child(anim_player)
+
+ # Create pulsing animation
+ var animation = Animation.new()
+ animation.name = "pulse"
+ animation.length = 1.0
+ animation.loop_mode = Animation.LOOP_LINEAR
+
+ # Scale property track
+ var scale_track = animation.add_track(Animation.TYPE_VALUE)
+ animation.track_set_path(scale_track, ".:scale")
+ animation.track_insert_key(scale_track, 0.0, Vector2(1.05, 1.05))
+ animation.track_insert_key(scale_track, 0.5, Vector2(1.1, 1.1))
+ animation.track_insert_key(scale_track, 1.0, Vector2(1.05, 1.05))
+
+ anim_player.add_animation("pulse", animation)
+ anim_player.play("pulse")
+
+func _start_shake_animation() -> void:
+ # Create animation player for shake effect
+ var anim_player = AnimationPlayer.new()
+ anim_player.name = "StateAnimationPlayer"
+
+ # Remove existing animation player if present
+ var existing_player = get_node_or_null("StateAnimationPlayer")
+ if existing_player:
+ remove_child(existing_player)
+ existing_player.queue_free()
+
+ add_child(anim_player)
+
+ # Create shake animation
+ var animation = Animation.new()
+ animation.name = "shake"
+ animation.length = 0.5
+ animation.loop_mode = Animation.LOOP_LINEAR
+
+ # Position property track
+ var position_track = animation.add_track(Animation.TYPE_VALUE)
+ animation.track_set_path(position_track, ".:position_offset")
+ animation.track_insert_key(position_track, 0.0, position_offset)
+ animation.track_insert_key(position_track, 0.1, position_offset + Vector2(2, 0))
+ animation.track_insert_key(position_track, 0.2, position_offset + Vector2(-2, 0))
+ animation.track_insert_key(position_track, 0.3, position_offset + Vector2(0, 2))
+ animation.track_insert_key(position_track, 0.4, position_offset + Vector2(0, -2))
+ animation.track_insert_key(position_track, 0.5, position_offset)
+
+ anim_player.add_animation("shake", animation)
+ anim_player.play("shake")
+
+func _stop_animations() -> void:
+ var anim_player = get_node_or_null("StateAnimationPlayer")
+ if anim_player:
+ anim_player.stop()
+
+# Update parameter value
+func set_parameter(parameter_name: String, value) -> void:
+ action_parameters[parameter_name] = value
+
+ # Update editor if it exists
+ if property_editors.has(parameter_name):
+ var editor = property_editors[parameter_name]
+ editor.set_value(value)
+
+ emit_signal("parameter_changed", self, parameter_name, value)
+
+# Get parameter value
+func get_parameter(parameter_name: String):
+ return action_parameters.get(parameter_name, null)
+
+# Handle node dragging
+func _on_node_dragged(from: Vector2, to: Vector2) -> void:
+ # Update position
+ position_offset = to
+
+# Handle node selection
+func _on_node_selected() -> void:
+ emit_signal("node_selected2", self)
+
+
+# Clear parameter fields
+func _clear_parameter_fields() -> void:
+ # Remove existing editors
+ for param_name in property_editors:
+ var editor = property_editors[param_name]
+ if editor.get_parent() == self:
+ remove_child(editor)
+ editor.queue_free()
+
+ property_editors.clear()
diff --git a/addons/cutscene_editor/editor/nodes/DialogueActionNode.gd b/addons/cutscene_editor/editor/nodes/DialogueActionNode.gd
new file mode 100644
index 0000000..4b0549e
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/DialogueActionNode.gd
@@ -0,0 +1,68 @@
+@tool
+class_name DialogueActionNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Node for DialogueAction
+
+func _init() -> void:
+ node_type = "dialogue"
+ node_id = "dialogue_" + str(randi())
+ title = "Dialogue"
+ modulate = Color(1.0, 1.0, 0.5) # Yellow
+ resizable=true
+ # One input and one output connection
+ var slot = 0
+ set_slot(slot, true, 0, Color(0, 0, 0), true, 0, Color(0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Initialize default parameters
+ action_parameters["character"] = ""
+ action_parameters["text"] = ""
+ action_parameters["duration"] = 0.0
+
+func _setup_parameter_fields() -> void:
+ # Character field
+ var x = VBoxContainer.new()
+ add_child(x)
+ var char_label = Label.new()
+ char_label.text = "Character:"
+ char_label.hide()
+ x.add_child(char_label)
+
+ var char_field = LineEdit.new()
+ char_field.text = action_parameters["character"]
+ char_field.connect("text_changed", _on_character_changed)
+ x.add_child(char_field)
+
+ # Text field
+ var text_label = Label.new()
+ text_label.text = "Text:"
+ x.add_child(text_label)
+
+ var text_field = TextEdit.new()
+ text_field.text = action_parameters["text"]
+ text_field.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ text_field.connect("text_changed", _on_text_changed)
+ x.add_child(text_field)
+
+ # Duration field
+ var duration_label = Label.new()
+ duration_label.text = "Duration:"
+ x.add_child(duration_label)
+
+ var duration_field = LineEdit.new()
+ duration_field.text = str(action_parameters["duration"])
+ duration_field.connect("text_changed", _on_duration_changed)
+ x.add_child(duration_field)
+
+func _on_character_changed(new_text: String) -> void:
+ set_parameter("character", new_text)
+
+func _on_text_changed() -> void:
+ var text_edit = get_child(get_child_count() - 2) # TextEdit is second to last child
+ set_parameter("text", text_edit.text)
+
+func _on_duration_changed(new_text: String) -> void:
+ var value = float(new_text) if new_text.is_valid_float() else 0.0
+ set_parameter("duration", value)
diff --git a/addons/cutscene_editor/editor/nodes/EntryNode.gd b/addons/cutscene_editor/editor/nodes/EntryNode.gd
new file mode 100644
index 0000000..3e73b02
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/EntryNode.gd
@@ -0,0 +1,20 @@
+@tool
+class_name EntryNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Entry point for the cutscene
+
+func _init() -> void:
+ node_type = "entry"
+ node_id = "entry_" + str(randi())
+ title = "Start"
+ modulate = Color(0.5, 1.0, 0.5) # Light green
+
+ # Entry node has no input connections
+ # Add one output connection point
+ var output_slot = 0
+ set_slot(output_slot, false, 0, Color(0, 0, 0, 0), true, 0, Color(0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Entry node has no parameter fields
diff --git a/addons/cutscene_editor/editor/nodes/ExitNode.gd b/addons/cutscene_editor/editor/nodes/ExitNode.gd
new file mode 100644
index 0000000..b31100c
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/ExitNode.gd
@@ -0,0 +1,20 @@
+@tool
+class_name ExitNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Exit point for the cutscene
+
+func _init() -> void:
+ node_type = "exit"
+ node_id = "exit_" + str(randi())
+ title = "End"
+ modulate = Color(1.0, 0.5, 0.5) # Light red
+
+ # Exit node has one input connection
+ # No output connections
+ var input_slot = 0
+ set_slot(input_slot, true, 0, Color(0, 0, 0), false, 0, Color(0, 0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Exit node has no parameter fields
diff --git a/addons/cutscene_editor/editor/nodes/MoveActionNode.gd b/addons/cutscene_editor/editor/nodes/MoveActionNode.gd
new file mode 100644
index 0000000..22418fa
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/MoveActionNode.gd
@@ -0,0 +1,88 @@
+@tool
+class_name MoveActionNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Node for MoveAction
+
+func _init() -> void:
+ node_type = "move"
+ node_id = "move_" + str(randi())
+ title = "Move"
+ modulate = Color(0.4, 0.6, 1.0) # Blue
+
+ # One input and one output connection
+ var slot = 0
+ set_slot(slot, true, 0, Color(0, 0, 0), true, 0, Color(0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Initialize default parameters
+ action_parameters["character"] = ""
+ action_parameters["target_x"] = 0.0
+ action_parameters["target_y"] = 0.0
+ action_parameters["speed"] = 100.0
+
+func _setup_parameter_fields() -> void:
+ # Character field
+ var char_label = Label.new()
+ char_label.text = "Character:"
+ add_child(char_label)
+
+ var char_field = LineEdit.new()
+ char_field.text = action_parameters["character"]
+ char_field.connect("text_changed", _on_character_changed)
+ add_child(char_field)
+
+ # Target position fields
+ var pos_label = Label.new()
+ pos_label.text = "Target Position:"
+ add_child(pos_label)
+
+ var pos_container = HBoxContainer.new()
+
+ var x_label = Label.new()
+ x_label.text = "X:"
+ pos_container.add_child(x_label)
+
+ var x_field = LineEdit.new()
+ x_field.text = str(action_parameters["target_x"])
+ x_field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ x_field.connect("text_changed", _on_target_x_changed)
+ pos_container.add_child(x_field)
+
+ var y_label = Label.new()
+ y_label.text = "Y:"
+ pos_container.add_child(y_label)
+
+ var y_field = LineEdit.new()
+ y_field.text = str(action_parameters["target_y"])
+ y_field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ y_field.connect("text_changed", _on_target_y_changed)
+ pos_container.add_child(y_field)
+
+ add_child(pos_container)
+
+ # Speed field
+ var speed_label = Label.new()
+ speed_label.text = "Speed:"
+ add_child(speed_label)
+
+ var speed_field = LineEdit.new()
+ speed_field.text = str(action_parameters["speed"])
+ speed_field.connect("text_changed", _on_speed_changed)
+ add_child(speed_field)
+
+func _on_character_changed(new_text: String) -> void:
+ set_parameter("character", new_text)
+
+func _on_target_x_changed(new_text: String) -> void:
+ var value = float(new_text) if new_text.is_valid_float() else 0.0
+ set_parameter("target_x", value)
+
+func _on_target_y_changed(new_text: String) -> void:
+ var value = float(new_text) if new_text.is_valid_float() else 0.0
+ set_parameter("target_y", value)
+
+func _on_speed_changed(new_text: String) -> void:
+ var value = float(new_text) if new_text.is_valid_float() else 100.0
+ set_parameter("speed", value)
diff --git a/addons/cutscene_editor/editor/nodes/ParallelGroupNode.gd b/addons/cutscene_editor/editor/nodes/ParallelGroupNode.gd
new file mode 100644
index 0000000..b781e19
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/ParallelGroupNode.gd
@@ -0,0 +1,126 @@
+@tool
+class_name ParallelGroupNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Node for grouping parallel actions
+
+# Special properties for parallel groups
+var input_connections: int = 3 # Number of input connection points
+var child_nodes: Array = [] # Child nodes contained within this group
+var is_container: bool = true # Flag to indicate this is a container node
+
+# Visual properties
+var container_rect: PanelContainer # Visual container for child nodes
+
+func _init() -> void:
+ node_type = "parallel"
+ node_id = "parallel_" + str(randi())
+ title = "Parallel Group"
+ modulate = Color(1.0, 0.6, 0.2) # Orange
+
+ # Set up slots for connections
+ _setup_slots()
+
+ # Set up container for child nodes
+ _setup_container()
+
+# Set up connection slots
+func _setup_slots() -> void:
+ # Multiple input connections for parallel actions
+ for i in range(input_connections):
+ set_slot(i, true, 0, Color(0, 0, 0), false, 0, Color(0, 0, 0, 0))
+
+ # Single output connection for sequential continuation
+ var output_slot = input_connections
+ set_slot(output_slot, false, 0, Color(0, 0, 0, 0), true, 0, Color(0, 0, 0))
+
+# Set up visual container for child nodes
+func _setup_container() -> void:
+ # Create container panel
+ container_rect = PanelContainer.new()
+ container_rect.name = "Container"
+ container_rect.anchor_right = 1
+ container_rect.anchor_bottom = 1
+ container_rect.margin_top = 30 # Leave space for title bar
+ container_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
+
+ # Set container style
+ var style = StyleBoxFlat.new()
+ style.bg_color = Color(0.9, 0.9, 0.9, 0.3)
+ style.border_color = Color(0.5, 0.5, 0.5, 0.5)
+ style.border_width_left = 1
+ style.border_width_top = 1
+ style.border_width_right = 1
+ style.border_width_bottom = 1
+ container_rect.add_theme_stylebox_override("panel", style)
+
+ add_child(container_rect)
+
+ # Create container for child nodes
+ var child_container = VBoxContainer.new()
+ child_container.name = "ChildContainer"
+ child_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ container_rect.add_child(child_container)
+
+# Add a child node to this parallel group
+func add_child_node(child_node: BaseGraphNode) -> void:
+ # Add to child nodes array
+ child_nodes.append(child_node)
+
+ # Add as child in scene tree
+ if container_rect and container_rect.has_node("ChildContainer"):
+ container_rect.get_node("ChildContainer").add_child(child_node)
+
+ # Update visual representation
+ _update_container_size()
+
+# Remove a child node from this parallel group
+func remove_child_node(child_node: BaseGraphNode) -> void:
+ # Remove from child nodes array
+ child_nodes.erase(child_node)
+
+ # Remove from scene tree
+ if child_node.get_parent() == container_rect.get_node("ChildContainer"):
+ container_rect.get_node("ChildContainer").remove_child(child_node)
+
+ # Update visual representation
+ _update_container_size()
+
+# Update container size based on child nodes
+func _update_container_size() -> void:
+ # Calculate required size based on child nodes
+ var required_height = 20 # Minimum height
+
+ if container_rect and container_rect.has_node("ChildContainer"):
+ var child_container = container_rect.get_node("ChildContainer")
+ for child in child_container.get_children():
+ if child is BaseGraphNode:
+ required_height += child.size.y + 5 # Add spacing
+
+ # Update container size
+ container_rect.custom_minimum_size.y = required_height
+
+# Handle node dragging
+func _on_node_dragged(from: Vector2, to: Vector2) -> void:
+ # Update position
+ position_offset = to
+
+ # Update child nodes if they're positioned relative to this node
+ for child in child_nodes:
+ # Child nodes should move with the parallel group
+ pass
+
+# Add more input connections if needed
+func add_input_connection() -> void:
+ var slot_index = input_connections
+ set_slot(slot_index, true, 0, Color(0, 0, 0), false, 0, Color(0, 0, 0, 0))
+ input_connections += 1
+
+# Get the output slot index
+func get_output_slot_index() -> int:
+ return input_connections
+
+# Check if a node can be added as a child
+func can_add_child_node(node: BaseGraphNode) -> bool:
+ # Can't add entry, exit, or other parallel groups as children
+ return node.node_type != "entry" and node.node_type != "exit" and node.node_type != "parallel"
diff --git a/addons/cutscene_editor/editor/nodes/TurnActionNode.gd b/addons/cutscene_editor/editor/nodes/TurnActionNode.gd
new file mode 100644
index 0000000..14f7232
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/TurnActionNode.gd
@@ -0,0 +1,63 @@
+@tool
+class_name TurnActionNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Node for TurnAction
+
+func _init() -> void:
+ node_type = "turn"
+ node_id = "turn_" + str(randi())
+ title = "Turn"
+ modulate = Color(0.5, 1.0, 0.5) # Green
+
+ # One input and one output connection
+ var slot = 0
+ set_slot(slot, true, 0, Color(0, 0, 0), true, 0, Color(0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Initialize default parameters
+ action_parameters["character"] = ""
+ action_parameters["target"] = ""
+ action_parameters["turn_speed"] = 2.0
+
+func _setup_parameter_fields() -> void:
+ # Character field
+ var char_label = Label.new()
+ char_label.text = "Character:"
+ add_child(char_label)
+
+ var char_field = LineEdit.new()
+ char_field.text = action_parameters["character"]
+ char_field.connect("text_changed", _on_character_changed)
+ add_child(char_field)
+
+ # Target field
+ var target_label = Label.new()
+ target_label.text = "Target:"
+ add_child(target_label)
+
+ var target_field = LineEdit.new()
+ target_field.text = action_parameters["target"]
+ target_field.connect("text_changed", _on_target_changed)
+ add_child(target_field)
+
+ # Turn speed field
+ var speed_label = Label.new()
+ speed_label.text = "Turn Speed:"
+ add_child(speed_label)
+
+ var speed_field = LineEdit.new()
+ speed_field.text = str(action_parameters["turn_speed"])
+ speed_field.connect("text_changed", _on_turn_speed_changed)
+ add_child(speed_field)
+
+func _on_character_changed(new_text: String) -> void:
+ set_parameter("character", new_text)
+
+func _on_target_changed(new_text: String) -> void:
+ set_parameter("target", new_text)
+
+func _on_turn_speed_changed(new_text: String) -> void:
+ var value = float(new_text) if new_text.is_valid_float() else 2.0
+ set_parameter("turn_speed", value)
diff --git a/addons/cutscene_editor/editor/nodes/WaitActionNode.gd b/addons/cutscene_editor/editor/nodes/WaitActionNode.gd
new file mode 100644
index 0000000..00bad61
--- /dev/null
+++ b/addons/cutscene_editor/editor/nodes/WaitActionNode.gd
@@ -0,0 +1,35 @@
+@tool
+class_name WaitActionNode
+extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
+
+# Node for WaitAction
+
+func _init() -> void:
+ node_type = "wait"
+ node_id = "wait_" + str(randi())
+ title = "Wait"
+ modulate = Color(0.7, 0.7, 0.7) # Gray
+
+ # One input and one output connection
+ var slot = 0
+ set_slot(slot, true, 0, Color(0, 0, 0), true, 0, Color(0, 0, 0))
+
+func _ready() -> void:
+ super._ready()
+ # Initialize default parameters
+ action_parameters["duration"] = 1.0
+
+func _setup_parameter_fields() -> void:
+ # Duration field
+ var duration_label = Label.new()
+ duration_label.text = "Duration:"
+ add_child(duration_label)
+
+ var duration_field = LineEdit.new()
+ duration_field.text = str(action_parameters["duration"])
+ duration_field.connect("text_changed", _on_duration_changed)
+ add_child(duration_field)
+
+func _on_duration_changed(new_text: String) -> void:
+ var value = float(new_text) if new_text.is_valid_float() else 1.0
+ set_parameter("duration", value)
diff --git a/addons/cutscene_editor/editor/resources/CutsceneResource.gd b/addons/cutscene_editor/editor/resources/CutsceneResource.gd
new file mode 100644
index 0000000..8486baf
--- /dev/null
+++ b/addons/cutscene_editor/editor/resources/CutsceneResource.gd
@@ -0,0 +1,121 @@
+@tool
+class_name CutsceneResource
+extends Resource
+
+# Resource for storing cutscene data
+
+# Properties
+@export var nodes: Array = [] # List of node data
+@export var connections: Array = [] # List of connection data
+@export var parallel_connections: Array = [] # Logical connections for parallel groups
+@export var metadata: Dictionary = {} # Additional metadata
+
+# Initialize the resource
+func _init() -> void:
+ nodes = []
+ connections = []
+ parallel_connections = []
+ metadata = {
+ "version": "1.0",
+ "created": Time.get_unix_time_from_system(),
+ "modified": Time.get_unix_time_from_system()
+ }
+
+# Add a node to the cutscene
+func add_node(node_data: Dictionary) -> void:
+ nodes.append(node_data)
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Remove a node from the cutscene
+func remove_node(node_name: String) -> void:
+ # Remove the node
+ for i in range(nodes.size()):
+ if nodes[i].has("name") and nodes[i]["name"] == node_name:
+ nodes.remove_at(i)
+ break
+
+ # Remove any connections to/from this node
+ var i = 0
+ while i < connections.size():
+ var conn = connections[i]
+ if conn["from_node"] == node_name or conn["to_node"] == node_name:
+ connections.remove_at(i)
+ else:
+ i += 1
+
+ # Remove any parallel connections to/from this node
+ i = 0
+ while i < parallel_connections.size():
+ var conn = parallel_connections[i]
+ if conn["from_node"] == node_name or conn["to_node"] == node_name:
+ parallel_connections.remove_at(i)
+ else:
+ i += 1
+
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Add a connection to the cutscene
+func add_connection(connection_data: Dictionary) -> void:
+ connections.append(connection_data)
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Remove a connection from the cutscene
+func remove_connection(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ for i in range(connections.size()):
+ var conn = connections[i]
+ if (conn["from_node"] == from_node and conn["from_port"] == from_port and
+ conn["to_node"] == to_node and conn["to_port"] == to_port):
+ connections.remove_at(i)
+ break
+
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Add a parallel connection to the cutscene
+func add_parallel_connection(connection_data: Dictionary) -> void:
+ parallel_connections.append(connection_data)
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Remove a parallel connection from the cutscene
+func remove_parallel_connection(from_node: String, from_port: int, to_node: String, to_port: int) -> void:
+ for i in range(parallel_connections.size()):
+ var conn = parallel_connections[i]
+ if (conn["from_node"] == from_node and conn["from_port"] == from_port and
+ conn["to_node"] == to_node and conn["to_port"] == to_port):
+ parallel_connections.remove_at(i)
+ break
+
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Get node by name
+func get_node_by_name(node_name: String) -> Dictionary:
+ for node in nodes:
+ if node.has("name") and node["name"] == node_name:
+ return node
+ return {}
+
+# Get all connections for a node
+func get_connections_for_node(node_name: String) -> Array:
+ var node_connections = []
+ for conn in connections:
+ if conn["from_node"] == node_name or conn["to_node"] == node_name:
+ node_connections.append(conn)
+ return node_connections
+
+# Get all parallel connections for a node
+func get_parallel_connections_for_node(node_name: String) -> Array:
+ var node_connections = []
+ for conn in parallel_connections:
+ if conn["from_node"] == node_name or conn["to_node"] == node_name:
+ node_connections.append(conn)
+ return node_connections
+
+# Clear all data
+func clear() -> void:
+ nodes.clear()
+ connections.clear()
+ parallel_connections.clear()
+ metadata["modified"] = Time.get_unix_time_from_system()
+
+# Update metadata
+func update_metadata() -> void:
+ metadata["modified"] = Time.get_unix_time_from_system()
diff --git a/addons/cutscene_editor/examples/example_cutscene.gd b/addons/cutscene_editor/examples/example_cutscene.gd
new file mode 100644
index 0000000..4dcb515
--- /dev/null
+++ b/addons/cutscene_editor/examples/example_cutscene.gd
@@ -0,0 +1,83 @@
+@tool
+extends Node2D
+
+# Example cutscene using the cutscene editor plugin
+
+# Character nodes
+@onready var character1: Node2D = $Character1
+@onready var character2: Node2D = $Character2
+
+# Cutscene manager
+var cutscene_manager: CutsceneManager
+
+func _ready() -> void:
+ # Initialize the cutscene system
+ setup_cutscene()
+
+ # Start the cutscene after a short delay to see the initial positions
+ var start_timer = Timer.new()
+ start_timer.wait_time = 1.0
+ start_timer.one_shot = true
+ start_timer.connect("timeout", start_cutscene)
+ add_child(start_timer)
+ start_timer.start()
+
+func setup_cutscene() -> void:
+ # Create the cutscene manager
+ cutscene_manager = CutsceneManager.new()
+ add_child(cutscene_manager)
+
+ # Connect to cutscene signals
+ cutscene_manager.connect("cutscene_started", _on_cutscene_started)
+ cutscene_manager.connect("cutscene_completed", _on_cutscene_completed)
+ cutscene_manager.connect("action_started", _on_action_started)
+ cutscene_manager.connect("action_completed", _on_action_completed)
+
+ # Create the action sequence as described in requirements
+
+ # 1. & 2. Characters move simultaneously
+ var parallel_moves = [
+ MoveAction.new(character1, Vector2(234, 591), 100.0), # Character1 moves
+ MoveAction.new(character2, Vector2(912, 235), 100.0) # Character2 moves
+ ]
+ cutscene_manager.add_parallel_actions(parallel_moves)
+
+ # 3. Character2 turns to face Character1
+ var turn_action = TurnAction.new(character2, character1, 1.0)
+ cutscene_manager.add_action(turn_action)
+
+ # 4. Character2 says dialogue
+ var dialogue_action = DialogueAction.new(character2, "Hello there, friend!", 2.0)
+ cutscene_manager.add_action(dialogue_action)
+
+ # 5. Character1 plays shocked animation (simulated with a wait since we don't have an actual animation)
+ var animation_action = WaitAction.new(1.0) # Simulate animation with wait
+ cutscene_manager.add_action(animation_action)
+
+ # Add a final dialogue from character1
+ var final_dialogue = DialogueAction.new(character1, "That was surprising!", 2.0)
+ cutscene_manager.add_action(final_dialogue)
+
+func start_cutscene() -> void:
+ print("Starting cutscene...")
+ cutscene_manager.start()
+
+func _on_cutscene_started() -> void:
+ print("Cutscene started!")
+
+func _on_cutscene_completed() -> void:
+ print("Cutscene completed!")
+ print("Final positions:")
+ print("Character1: %s" % character1.position)
+ print("Character2: %s" % character2.position)
+
+func _on_action_started(action: Action) -> void:
+ print("Action started: %s" % action.name)
+
+func _on_action_completed(action: Action) -> void:
+ print("Action completed: %s" % action.name)
+
+# Clean up when the node is removed
+func _exit_tree() -> void:
+ if cutscene_manager:
+ cutscene_manager.queue_free()
\ No newline at end of file
diff --git a/addons/cutscene_editor/examples/example_cutscene.tscn b/addons/cutscene_editor/examples/example_cutscene.tscn
new file mode 100644
index 0000000..d38d25f
--- /dev/null
+++ b/addons/cutscene_editor/examples/example_cutscene.tscn
@@ -0,0 +1,18 @@
+[gd_scene load_steps=2 format=3 uid="uid://example_cutscene"]
+
+[ext_resource type="Script" path="res://addons/cutscene_editor/examples/example_cutscene.gd" id="1"]
+
+[node name="ExampleCutscene" type="Node2D"]
+script = ExtResource("1")
+
+[node name="Character1" type="Node2D" parent="."]
+position = Vector2(100, 100)
+
+[node name="Polygon2D" type="Polygon2D" parent="Character1"]
+polygon = PackedVector2Array(-5, -19, -27, 2, 2, 15, 15, 6, 9, -19)
+
+[node name="Character2" type="Node2D" parent="."]
+position = Vector2(200, 100)
+
+[node name="Polygon2D2" type="Polygon2D" parent="Character2"]
+polygon = PackedVector2Array(10, -25, -43, 1, -14, 40, 48, 13)
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_animation.svg b/addons/cutscene_editor/icons/icon_animation.svg
new file mode 100644
index 0000000..cc4c508
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_animation.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_animation.svg.import b/addons/cutscene_editor/icons/icon_animation.svg.import
new file mode 100644
index 0000000..4fb52cb
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_animation.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ow1w5ad2w2s"
+path="res://.godot/imported/icon_animation.svg-241698a15be9fbca77a14e45efcb07c0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_animation.svg"
+dest_files=["res://.godot/imported/icon_animation.svg-241698a15be9fbca77a14e45efcb07c0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_dialogue.svg b/addons/cutscene_editor/icons/icon_dialogue.svg
new file mode 100644
index 0000000..bf3024e
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_dialogue.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_dialogue.svg.import b/addons/cutscene_editor/icons/icon_dialogue.svg.import
new file mode 100644
index 0000000..db25272
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_dialogue.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cwqk0og08i1a6"
+path="res://.godot/imported/icon_dialogue.svg-a0f84654cbf69d8bc485290ace9e2d01.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_dialogue.svg"
+dest_files=["res://.godot/imported/icon_dialogue.svg-a0f84654cbf69d8bc485290ace9e2d01.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_entry.svg b/addons/cutscene_editor/icons/icon_entry.svg
new file mode 100644
index 0000000..60c68df
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_entry.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_entry.svg.import b/addons/cutscene_editor/icons/icon_entry.svg.import
new file mode 100644
index 0000000..0ff10de
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_entry.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dbimchm3l8l45"
+path="res://.godot/imported/icon_entry.svg-f83a920a49d4a03c426acaebbf9664c0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_entry.svg"
+dest_files=["res://.godot/imported/icon_entry.svg-f83a920a49d4a03c426acaebbf9664c0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_exit.svg b/addons/cutscene_editor/icons/icon_exit.svg
new file mode 100644
index 0000000..64b478f
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_exit.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_exit.svg.import b/addons/cutscene_editor/icons/icon_exit.svg.import
new file mode 100644
index 0000000..dd09169
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_exit.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://doum444mw5npx"
+path="res://.godot/imported/icon_exit.svg-af6dcff1443188587677d8a810d422d2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_exit.svg"
+dest_files=["res://.godot/imported/icon_exit.svg-af6dcff1443188587677d8a810d422d2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_move.svg b/addons/cutscene_editor/icons/icon_move.svg
new file mode 100644
index 0000000..381d056
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_move.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_move.svg.import b/addons/cutscene_editor/icons/icon_move.svg.import
new file mode 100644
index 0000000..4062037
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_move.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d32631g3r7so7"
+path="res://.godot/imported/icon_move.svg-8960b11003071765119b102b0571d086.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_move.svg"
+dest_files=["res://.godot/imported/icon_move.svg-8960b11003071765119b102b0571d086.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_parallel.svg b/addons/cutscene_editor/icons/icon_parallel.svg
new file mode 100644
index 0000000..30816c7
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_parallel.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_parallel.svg.import b/addons/cutscene_editor/icons/icon_parallel.svg.import
new file mode 100644
index 0000000..3912f8d
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_parallel.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://buuw640avujpg"
+path="res://.godot/imported/icon_parallel.svg-a5bbaaae40552592cadd6e57e5639c21.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_parallel.svg"
+dest_files=["res://.godot/imported/icon_parallel.svg-a5bbaaae40552592cadd6e57e5639c21.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_turn.svg b/addons/cutscene_editor/icons/icon_turn.svg
new file mode 100644
index 0000000..16a856d
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_turn.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_turn.svg.import b/addons/cutscene_editor/icons/icon_turn.svg.import
new file mode 100644
index 0000000..35cf67f
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_turn.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dg05cb53hbflj"
+path="res://.godot/imported/icon_turn.svg-9e31b9ffad1d239c195dfe0ce34464d9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_turn.svg"
+dest_files=["res://.godot/imported/icon_turn.svg-9e31b9ffad1d239c195dfe0ce34464d9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/icons/icon_wait.svg b/addons/cutscene_editor/icons/icon_wait.svg
new file mode 100644
index 0000000..d6ce385
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_wait.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/addons/cutscene_editor/icons/icon_wait.svg.import b/addons/cutscene_editor/icons/icon_wait.svg.import
new file mode 100644
index 0000000..f579992
--- /dev/null
+++ b/addons/cutscene_editor/icons/icon_wait.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cy5siww7m3phl"
+path="res://.godot/imported/icon_wait.svg-9bc30c71bf95afaa32c61cb9836ac838.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cutscene_editor/icons/icon_wait.svg"
+dest_files=["res://.godot/imported/icon_wait.svg-9bc30c71bf95afaa32c61cb9836ac838.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/cutscene_editor/plugin.cfg b/addons/cutscene_editor/plugin.cfg
new file mode 100644
index 0000000..1b91c1b
--- /dev/null
+++ b/addons/cutscene_editor/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Cutscene Editor"
+description="A visual graph-based editor for designing point-and-click adventure game cutscenes"
+author="Adventure AI Team"
+version="1.0"
+script="CutsceneEditorPlugin.gd"
\ No newline at end of file
diff --git a/cutscene_manager_design.md b/cutscene_manager_design.md
deleted file mode 100644
index 9d0e413..0000000
--- a/cutscene_manager_design.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# CutsceneManager Design
-
-## Overview
-The `CutsceneManager` is the central orchestrator for executing cutscenes. It manages both sequential and parallel actions, tracks their completion, and controls the flow of the cutscene.
-
-## Class Structure
-
-```gdscript
-class_name CutsceneManager
-extends Node
-
-# Action queues
-var sequential_actions: Array # Actions that run one after another
-var current_action: Action # The action currently being executed
-var action_index: int = 0 # Index of the next sequential action
-
-# Parallel actions
-var parallel_actions: Array # Groups of actions running simultaneously
-var active_parallel_groups: Array # Currently running parallel action groups
-
-# State management
-enum State {
- IDLE, # Not running any cutscene
- RUNNING, # Currently executing a cutscene
- PAUSED # Cutscene is paused
-}
-
-var state: int = State.IDLE
-var is_active: bool = false
-
-# Signals
-signal cutscene_started()
-signal cutscene_completed()
-signal cutscene_paused()
-signal cutscene_resumed()
-signal action_started(action)
-signal action_completed(action)
-
-# Public methods
-func start() -> void:
- # Start executing the cutscene
- pass
-
-func pause() -> void:
- # Pause the current cutscene
- pass
-
-func resume() -> void:
- # Resume a paused cutscene
- pass
-
-func stop() -> void:
- # Stop the current cutscene and reset
- pass
-
-func add_action(action: Action) -> void:
- # Add a single action to the sequential queue
- pass
-
-func add_parallel_actions(actions: Array) -> void:
- # Add a group of actions to run in parallel
- pass
-
-# Internal methods
-func _process(delta: float) -> void:
- # Main update loop
- pass
-
-func _execute_next_action() -> void:
- # Start executing the next sequential action
- pass
-
-func _check_parallel_groups() -> void:
- # Check if any parallel action groups have completed
- pass
-
-func _on_action_completed(action: Action) -> void:
- # Handle action completion
- pass
-
-func _reset() -> void:
- # Reset the manager to initial state
- pass
-```
-
-## Sequential Execution System
-
-The sequential execution system runs actions one after another:
-
-1. Actions are added to the `sequential_actions` queue
-2. When the cutscene starts, the first action is executed
-3. When an action completes, the next one in the queue is started
-4. The cutscene completes when all sequential actions are finished
-
-```gdscript
-# Example flow:
-# 1. Add actions: [MoveAction, DialogueAction, AnimationAction]
-# 2. Start cutscene
-# 3. MoveAction starts and runs
-# 4. When MoveAction completes, DialogueAction starts
-# 5. When DialogueAction completes, AnimationAction starts
-# 6. When AnimationAction completes, cutscene ends
-```
-
-## Parallel Execution System
-
-The parallel execution system runs multiple actions simultaneously:
-
-1. Groups of actions are added as parallel groups
-2. All actions in a group start at the same time
-3. The manager waits for all actions in the group to complete
-4. When all actions in a group complete, the next sequential action can proceed
-
-```gdscript
-# Example flow:
-# 1. Add sequential action: MoveAction (character1)
-# 2. Add parallel group: [MoveAction (character2), MoveAction (character3)]
-# 3. Add sequential action: DialogueAction
-#
-# Execution:
-# 1. MoveAction (character1) runs and completes
-# 2. Both MoveAction (character2) and MoveAction (character3) start simultaneously
-# 3. When both complete, DialogueAction starts
-# 4. When DialogueAction completes, cutscene ends
-```
-
-## Action Group Management
-
-Parallel actions are managed in groups:
-
-```gdscript
-# Structure for parallel action groups:
-{
- "actions": [Action, Action, ...], # The actions in this group
- "completed_count": 0, # How many actions have completed
- "total_count": 0 # Total number of actions in group
-}
-```
-
-## State Transitions
-
-The CutsceneManager has these states:
-- `IDLE`: Not running any cutscene
-- `RUNNING`: Executing a cutscene
-- `PAUSED`: Cutscene is temporarily paused
-
-State transitions:
-- IDLE → RUNNING: When `start()` is called
-- RUNNING → PAUSED: When `pause()` is called
-- PAUSED → RUNNING: When `resume()` is called
-- RUNNING → IDLE: When cutscene completes or `stop()` is called
-- PAUSED → IDLE: When `stop()` is called
-
-## Error Handling
-
-The manager should handle these error cases:
-- Attempting to start a cutscene that's already running
-- Adding actions while a cutscene is running
-- Actions that fail during execution
-- Empty action queues
-
-## Integration with Godot Engine
-
-The CutsceneManager should:
-- Inherit from `Node` to be added to the scene tree
-- Use `_process()` for frame-based updates
-- Connect to action signals for completion notifications
-- Be easily instantiated and configured in the editor or via code
-
-This design provides a robust foundation for managing complex cutscene sequences with both sequential and parallel execution patterns.
\ No newline at end of file
diff --git a/cutscene_system_architecture.md b/cutscene_system_architecture.md
deleted file mode 100644
index 1b8c87c..0000000
--- a/cutscene_system_architecture.md
+++ /dev/null
@@ -1,194 +0,0 @@
-# Cutscene System Architecture
-
-## Overview
-This document provides a visual representation of the cutscene system architecture using Mermaid diagrams.
-
-## System Components
-
-```mermaid
-graph TD
- A[CutsceneManager] --> B[Sequential Actions Queue]
- A --> C[Parallel Actions Groups]
- A --> D[State Management]
- A --> E[Signal System]
-
- B --> F[Action 1]
- B --> G[Action 2]
- B --> H[Action 3]
-
- C --> I[Parallel Group 1]
- C --> J[Parallel Group 2]
-
- I --> K[Action A]
- I --> L[Action B]
-
- J --> M[Action C]
- J --> N[Action D]
- J --> O[Action E]
-
- F --> P[Action Base Class]
- G --> P
- H --> P
- K --> P
- L --> P
- M --> P
- N --> P
- O --> P
-
- P --> Q[Start Method]
- P --> R[Update Method]
- P --> S[Is Completed Method]
- P --> T[Stop Method]
-```
-
-## Action State Flow
-
-```mermaid
-stateDiagram-v2
- [*] --> PENDING
- PENDING --> RUNNING: start()
- RUNNING --> COMPLETED: _set_completed()
- RUNNING --> FAILED: _set_failed()
- RUNNING --> STOPPED: stop()
- FAILED --> [*]
- STOPPED --> [*]
- COMPLETED --> [*]
-```
-
-## CutsceneManager State Flow
-
-```mermaid
-stateDiagram-v2
- [*] --> IDLE
- IDLE --> RUNNING: start()
- RUNNING --> PAUSED: pause()
- PAUSED --> RUNNING: resume()
- RUNNING --> IDLE: stop() or completion
- PAUSED --> IDLE: stop()
-```
-
-## Action Execution Flow
-
-```mermaid
-sequenceDiagram
- participant CM as CutsceneManager
- participant SA as SequentialAction
- participant PA as ParallelActionA
- participant PB as ParallelActionB
-
- CM->>SA: start()
- SA->>CM: started signal
- CM->>SA: update() each frame
- SA->>CM: completed signal
-
- CM->>PA: start()
- CM->>PB: start()
- PA->>CM: started signal
- PB->>CM: started signal
- CM->>PA: update() each frame
- CM->>PB: update() each frame
- PA->>CM: completed signal
- Note over CM: Waiting for all parallel actions
- PB->>CM: completed signal
- Note over CM: All parallel actions completed
-```
-
-## Class Hierarchy
-
-```mermaid
-classDiagram
- class Action {
- +State state
- +String name
- +started()
- +completed()
- +failed()
- +start()
- +update()
- +is_completed()
- +stop()
- }
-
- class MoveAction {
- +Node2D character
- +Vector2 target_position
- +float speed
- +start()
- +update()
- +is_completed()
- }
-
- class TurnAction {
- +Node2D character
- +Variant target
- +float turn_speed
- +start()
- +update()
- +is_completed()
- }
-
- class DialogueAction {
- +Node2D character
- +String text
- +float duration
- +start()
- +update()
- +is_completed()
- }
-
- class AnimationAction {
- +Node2D character
- +String animation_name
- +bool loop
- +start()
- +update()
- +is_completed()
- }
-
- class WaitAction {
- +float duration
- +float elapsed_time
- +start()
- +update()
- +is_completed()
- }
-
- class CutsceneManager {
- +Array sequential_actions
- +Array parallel_groups
- +State state
- +start()
- +pause()
- +resume()
- +stop()
- +add_action()
- +add_parallel_actions()
- +_process()
- }
-
- Action <|-- MoveAction
- Action <|-- TurnAction
- Action <|-- DialogueAction
- Action <|-- AnimationAction
- Action <|-- WaitAction
- CutsceneManager --> Action
-```
-
-## Signal Connection Flow
-
-```mermaid
-graph LR
- A[CutsceneManager] -- Connects to --> B[Action Signals]
- B -- started --> A
- B -- completed --> A
- B -- failed --> A
- B -- stopped --> A
-
- A -- Manages --> C[Sequential Execution]
- A -- Manages --> D[Parallel Execution]
-
- D -- Tracks Completion --> E[Parallel Group Counter]
- E -- Notifies When Complete --> A
-```
-
-This architecture provides a clear, modular design that supports both sequential and parallel action execution while maintaining extensibility for new action types.
\ No newline at end of file
diff --git a/cutscene_system_design.md b/cutscene_system_design.md
deleted file mode 100644
index bb08470..0000000
--- a/cutscene_system_design.md
+++ /dev/null
@@ -1,116 +0,0 @@
-# Cutscene System Design for 2D Point-and-Click Adventure Game
-
-## Overview
-This document outlines the design for a flexible cutscene system in Godot 4.3. The system will manage actions that occur in sequence or in parallel, with support for time-based actions like character movement.
-
-## Core Architecture
-
-### 1. CutsceneManager
-The main orchestrator that manages the execution of cutscene actions.
-
-```gdscript
-# Core responsibilities:
-# - Managing the action queue
-# - Handling sequential and parallel execution
-# - Tracking action completion
-# - Providing an interface for starting/stopping cutscenes
-```
-
-### 2. Base Action Class
-An abstract base class that all actions will inherit from.
-
-```gdscript
-# Core responsibilities:
-# - Defining common interface for all actions
-# - Managing action state (pending, running, completed)
-# - Providing start/stop functionality
-# - Handling completion callbacks
-```
-
-### 3. Action Execution System
-Two main execution modes:
-- **Sequential**: Actions execute one after another
-- **Parallel**: Multiple actions execute simultaneously
-
-## Action Types
-
-### Core Action Types
-1. **MoveAction**: Moves a character to a specific position
-2. **DialogueAction**: Displays dialogue text
-3. **AnimationAction**: Plays a specific animation
-4. **TurnAction**: Makes a character turn to face a direction or another character
-5. **WaitAction**: Pauses execution for a specified time
-
-### Extensible Design
-The system will be designed to easily add new action types by inheriting from the base Action class.
-
-## Implementation Details
-
-### Action State Management
-Each action will have these states:
-- `PENDING`: Action is waiting to be executed
-- `RUNNING`: Action is currently executing
-- `COMPLETED`: Action has finished executing
-
-### Completion System
-Actions will use Godot's signal system to notify when they're completed:
-- Each action emits a `completed` signal when finished
-- The CutsceneManager listens for these signals to determine when to proceed
-
-### Parallel Execution
-For parallel actions:
-- Multiple actions start at the same time
-- The CutsceneManager waits for all actions to complete before proceeding
-- Uses a counter to track how many parallel actions are still running
-
-## Example Usage
-
-The system should support scripts like this:
-
-```gdscript
-# Create a cutscene
-var cutscene = CutsceneManager.new()
-
-# Add sequential actions
-cutscene.add_action(MoveAction.new(character1, Vector2(234, 591)))
-cutscene.add_action(DialogueAction.new(character1, "Hello there!"))
-
-# Add parallel actions
-var parallel_group = [
- MoveAction.new(character1, Vector2(234, 591)),
- MoveAction.new(character2, Vector2(912, 235))
-]
-cutscene.add_parallel_actions(parallel_group)
-
-# Add more sequential actions
-cutscene.add_action(TurnAction.new(character2, character1))
-cutscene.add_action(DialogueAction.new(character2, "Nice to meet you!"))
-cutscene.add_action(AnimationAction.new(character1, "shocked"))
-
-# Start the cutscene
-cutscene.start()
-```
-
-## File Structure
-```
-/cutscene/
- ├── CutsceneManager.gd
- ├── actions/
- │ ├── Action.gd (base class)
- │ ├── MoveAction.gd
- │ ├── DialogueAction.gd
- │ ├── AnimationAction.gd
- │ ├── TurnAction.gd
- │ └── WaitAction.gd
- └── examples/
- └── sample_cutscene.gd
-```
-
-## Extensibility
-To add new action types:
-1. Create a new class that inherits from `Action.gd`
-2. Implement the required methods (`start()`, `is_completed()`, etc.)
-3. Add any specific logic for the action type
-4. Use in cutscenes like any other action
-
-This design provides a solid foundation for a flexible cutscene system that can handle both sequential and parallel actions while being easily extensible for new action types.
\ No newline at end of file
diff --git a/cutscene_system_summary.md b/cutscene_system_summary.md
deleted file mode 100644
index f180618..0000000
--- a/cutscene_system_summary.md
+++ /dev/null
@@ -1,148 +0,0 @@
-# Cutscene System Summary
-
-## Overview
-This document provides a comprehensive summary of the cutscene system designed for a 2D point-and-click adventure game in Godot 4.3. The system supports both sequential and parallel action execution, with a focus on extensibility and dynamic behavior.
-
-## System Architecture
-
-### Core Components
-
-1. **CutsceneManager**
- - Central orchestrator for all cutscene actions
- - Manages sequential and parallel action execution
- - Handles action completion and state transitions
- - Provides signals for cutscene events
-
-2. **Action (Base Class)**
- - Abstract base class for all action types
- - Defines common interface and state management
- - Implements signal-based completion system
- - Supports callbacks for flexible chaining
-
-3. **Specific Action Types**
- - MoveAction: Character movement to specific positions
- - TurnAction: Character rotation to face targets
- - DialogueAction: Text display for conversations
- - AnimationAction: Playing character animations
- - WaitAction: Time-based delays
-
-### Key Features
-
-1. **Sequential Execution**
- - Actions execute one after another in order
- - Each action must complete before the next begins
- - Supports complex linear story sequences
-
-2. **Parallel Execution**
- - Multiple actions can run simultaneously
- - System waits for all actions in a group to complete
- - Enables synchronized character movements and actions
-
-3. **Dynamic Behavior**
- - Actions are not immediate; they take time to complete
- - Frame-based updates for smooth animations
- - Asynchronous completion through signal system
-
-4. **Extensibility**
- - Easy to add new action types by extending the base Action class
- - Plugin architecture for complex extensions
- - Custom event and callback systems
-
-## Implementation Details
-
-### File Structure
-```
-/cutscene/
- ├── CutsceneManager.gd
- ├── Action.gd (base class)
- ├── actions/
- │ ├── MoveAction.gd
- │ ├── TurnAction.gd
- │ ├── DialogueAction.gd
- │ ├── AnimationAction.gd
- │ └── WaitAction.gd
- └── examples/
- └── sample_cutscene.gd
-```
-
-### Example Usage
-
-The system supports complex sequences like the one described in the requirements:
-
-```gdscript
-# 1. Character1 moves to 234, 591
-# 2. At the same time Character2 moves to 912, 235
-# 3. When both arrive, character2 turns to character1
-# 4. Character2 says a dialogue line to character1
-# 5. A special animation (shocked) is played for character1
-
-var cutscene = CutsceneManager.new()
-
-# Add parallel movements
-var moves = [
- MoveAction.new(character1, Vector2(234, 591)),
- MoveAction.new(character2, Vector2(912, 235))
-]
-cutscene.add_parallel_actions(moves)
-
-// Add sequential actions
-cutscene.add_action(TurnAction.new(character2, character1))
-cutscene.add_action(DialogueAction.new(character2, "Hello there!"))
-cutscene.add_action(AnimationAction.new(character1, "shocked"))
-
-cutscene.start()
-```
-
-## Technical Design
-
-### State Management
-- Actions have three states: PENDING, RUNNING, COMPLETED
-- CutsceneManager tracks overall state: IDLE, RUNNING, PAUSED
-- Proper state transitions with signal notifications
-
-### Completion System
-- Signal-based completion notifications
-- Parallel action group tracking
-- Error handling and recovery mechanisms
-- Callback support for flexible action chaining
-
-### Performance Considerations
-- Object pooling support for frequently created actions
-- Efficient signal connection management
-- Minimal overhead for inactive actions
-- Frame-based updates for smooth animations
-
-## Extensibility Features
-
-### Adding New Actions
-1. Create a new class extending Action
-2. Implement required methods (start, is_completed)
-3. Add custom logic in update() if needed
-4. Use the action in cutscenes like any other
-
-### System Extensions
-- Plugin architecture for complex features
-- Custom event system for dynamic triggers
-- Integration points with game state management
-- Save/load support for persistent cutscenes
-
-## Benefits
-
-1. **Flexibility**: Supports both simple linear sequences and complex parallel actions
-2. **Extensibility**: Easy to add new action types and system features
-3. **Integration**: Designed to work with existing Godot systems
-4. **Performance**: Optimized for smooth gameplay and animations
-5. **Maintainability**: Clean separation of concerns and clear interfaces
-
-## Next Steps
-
-To implement this system:
-
-1. Create the base Action class
-2. Implement the CutsceneManager
-3. Develop the core action types
-4. Add the completion and callback system
-5. Create sample cutscenes for testing
-6. Extend with custom actions as needed
-
-This design provides a solid foundation for a powerful, flexible cutscene system that can handle the requirements of a 2D point-and-click adventure game while remaining extensible for future needs.
\ No newline at end of file
diff --git a/extensibility_guide.md b/extensibility_guide.md
deleted file mode 100644
index 8ca0840..0000000
--- a/extensibility_guide.md
+++ /dev/null
@@ -1,300 +0,0 @@
-# Cutscene System Extensibility Guide
-
-## Overview
-This document explains how to extend the cutscene system with new action types, features, and customizations while maintaining compatibility with the existing architecture.
-
-## Adding New Action Types
-
-### Basic Extension Process
-1. Create a new class that extends `Action`
-2. Implement the required methods (`start()`, `is_completed()`)
-3. Optionally implement `update()` and `stop()`
-4. Add any custom properties or methods needed
-
-### Example: Custom FadeAction
-```gdscript
-class_name FadeAction
-extends Action
-
-# Custom properties
-var target_node: CanvasItem
-var target_alpha: float
-var fade_speed: float = 1.0
-var start_alpha: float = 1.0
-
-func _init(node: CanvasItem, alpha: float, speed: float = 1.0):
- target_node = node
- target_alpha = alpha
- fade_speed = speed
- name = "FadeAction"
-
-func start() -> void:
- if target_node == null:
- ._set_failed("Target node is null")
- return
-
- start_alpha = target_node.modulate.a
- ._set_running()
-
-func update(delta: float) -> void:
- if state != State.RUNNING:
- return
-
- if target_node == null:
- ._set_failed("Target node was destroyed")
- return
-
- # Calculate new alpha value
- var current_alpha = target_node.modulate.a
- var alpha_diff = target_alpha - current_alpha
- var alpha_change = sign(alpha_diff) * fade_speed * delta
-
- # Check if we've reached the target
- if abs(alpha_change) >= abs(alpha_diff):
- # Set final alpha and complete
- var new_modulate = target_node.modulate
- new_modulate.a = target_alpha
- target_node.modulate = new_modulate
- ._set_completed()
- else:
- # Apply incremental change
- var new_modulate = target_node.modulate
- new_modulate.a += alpha_change
- target_node.modulate = new_modulate
-
-func is_completed() -> bool:
- return state == State.COMPLETED
-```
-
-## Customizing the CutsceneManager
-
-### Adding New Features
-The CutsceneManager can be extended with new functionality:
-
-```gdscript
-# Extended CutsceneManager with additional features
-class_name ExtendedCutsceneManager
-extends CutsceneManager
-
-# Custom properties
-var skip_enabled: bool = true
-var auto_skip_delay: float = 0.0
-
-# Custom signals
-signal cutscene_skipped()
-
-# Custom methods
-func skip_cutscene() -> void:
- if not skip_enabled:
- return
-
- emit_signal("cutscene_skipped")
- stop()
-
-func set_auto_skip(seconds: float) -> void:
- auto_skip_delay = seconds
- # Implementation for auto-skip functionality
-```
-
-### Plugin Architecture
-For complex extensions, consider a plugin-style architecture:
-
-```gdscript
-# Plugin interface
-class_name CutscenePlugin
-extends RefCounted
-
-func initialize(manager: CutsceneManager) -> void:
- # Initialize plugin with the manager
- pass
-
-func process_action(action: Action, delta: float) -> void:
- # Process action each frame
- pass
-
-func cleanup() -> void:
- # Clean up when cutscene ends
- pass
-
-# Example plugin for debugging
-class_name DebugCutscenePlugin
-extends CutscenePlugin
-
-func process_action(action: Action, delta: float) -> void:
- if action.state == Action.State.RUNNING:
- print("Action %s is running" % action.name)
-```
-
-## Integration with External Systems
-
-### Game State Integration
-Connect the cutscene system to your game's state management:
-
-```gdscript
-# Integration with a game state manager
-class_name GameStateIntegratedCutsceneManager
-extends CutsceneManager
-
-var game_state_manager: GameStateManager
-
-func start() -> void:
- # Notify game state manager
- if game_state_manager:
- game_state_manager.set_state(GameState.CUTSCENE)
-
- # Call parent start method
- super().start()
-
-func _on_cutscene_completed() -> void:
- # Notify game state manager
- if game_state_manager:
- game_state_manager.set_state(GameState.PLAYING)
-
- # Call parent completion handler
- super()._on_cutscene_completed()
-```
-
-### Save/Load System Integration
-Add support for saving and loading cutscene states:
-
-```gdscript
-class_name SaveableCutsceneManager
-extends CutsceneManager
-
-func save_state() -> Dictionary:
- return {
- "current_action_index": action_index,
- "sequential_actions": _serialize_actions(sequential_actions),
- "parallel_groups": _serialize_parallel_groups(active_parallel_groups),
- "state": state
- }
-
-func load_state(data: Dictionary) -> void:
- action_index = data["current_action_index"]
- sequential_actions = _deserialize_actions(data["sequential_actions"])
- active_parallel_groups = _deserialize_parallel_groups(data["parallel_groups"])
- state = data["state"]
-```
-
-## Performance Optimization Extensions
-
-### Action Pooling
-Implement object pooling for frequently created actions:
-
-```gdscript
-class_name PooledCutsceneManager
-extends CutsceneManager
-
-var action_pools: Dictionary = {}
-
-func get_pooled_action(action_type: String, params: Array) -> Action:
- if not action_pools.has(action_type):
- action_pools[action_type] = []
-
- var pool = action_pools[action_type]
- if pool.size() > 0:
- # Reuse existing action
- var action = pool.pop_back()
- # Reinitialize with new parameters
- action.reinitialize(params)
- return action
- else:
- # Create new action
- return _create_action(action_type, params)
-
-func return_action_to_pool(action: Action) -> void:
- var action_type = action.get_class()
- if not action_pools.has(action_type):
- action_pools[action_type] = []
-
- action.reset()
- action_pools[action_type].append(action)
-```
-
-## Custom Action Composition
-
-### Action Groups
-Create reusable action groups for common sequences:
-
-```gdscript
-class_name ActionGroup
-extends Action
-
-var actions: Array
-var current_action_index: int = 0
-
-func _init(group_actions: Array):
- actions = group_actions
- name = "ActionGroup"
-
-func start() -> void:
- current_action_index = 0
- if actions.size() > 0:
- _execute_action(actions[0])
- else:
- ._set_completed()
-
-func _execute_action(action: Action) -> void:
- action.connect("completed", _on_sub_action_completed.bind(action))
- action.start()
-
-func _on_sub_action_completed(action: Action) -> void:
- current_action_index += 1
- if current_action_index < actions.size():
- _execute_action(actions[current_action_index])
- else:
- ._set_completed()
-```
-
-## Event-Driven Extensions
-
-### Custom Events
-Add support for custom events that can trigger actions:
-
-```gdscript
-class_name EventDrivenCutsceneManager
-extends CutsceneManager
-
-var event_listeners: Dictionary = {}
-
-func listen_for_event(event_name: String, action: Action) -> void:
- if not event_listeners.has(event_name):
- event_listeners[event_name] = []
-
- event_listeners[event_name].append(action)
-
-func trigger_event(event_name: String) -> void:
- if event_listeners.has(event_name):
- for action in event_listeners[event_name]:
- # Add action to current sequence or execute immediately
- add_action(action)
-```
-
-## Best Practices for Extensions
-
-### 1. Maintain Compatibility
-- Always call parent methods when overriding
-- Keep the same method signatures
-- Don't change the core behavior of existing methods
-
-### 2. Use Composition Over Inheritance
-- Prefer adding functionality through plugins or components
-- Keep inheritance hierarchies shallow
-- Use interfaces where possible
-
-### 3. Provide Clear Extension Points
-- Document which methods are safe to override
-- Provide virtual methods for customization
-- Use signals for loose coupling
-
-### 4. Handle Errors Gracefully
-- Always check for null references
-- Provide meaningful error messages
-- Implement fallback behaviors
-
-### 5. Optimize for Performance
-- Reuse objects when possible
-- Avoid unnecessary processing
-- Profile extensions for performance impact
-
-This extensibility guide provides a framework for expanding the cutscene system while maintaining its core functionality and ease of use.
\ No newline at end of file
diff --git a/implementation_roadmap.md b/implementation_roadmap.md
deleted file mode 100644
index 375cef4..0000000
--- a/implementation_roadmap.md
+++ /dev/null
@@ -1,229 +0,0 @@
-# Cutscene System Implementation Roadmap
-
-## Overview
-This document outlines a step-by-step roadmap for implementing the cutscene system in Godot 4.3, based on the architectural designs created.
-
-## Phase 1: Foundation
-
-### 1.1 Base Action Class
-- Create `Action.gd` base class
-- Implement state management (PENDING, RUNNING, COMPLETED)
-- Add core signals (started, completed, failed)
-- Implement basic methods (start, update, is_completed, stop)
-
-### 1.2 CutsceneManager Core
-- Create `CutsceneManager.gd`
-- Implement sequential action queue
-- Add action addition methods
-- Create basic state management (IDLE, RUNNING)
-
-## Phase 2: Core Action Types
-
-### 2.1 WaitAction
-- Simplest action to implement and test
-- Validates the basic action system
-- Tests completion signaling
-
-### 2.2 MoveAction
-- Implement character movement logic
-- Add position calculation and frame-based updates
-- Test with different speeds and distances
-
-### 2.3 TurnAction
-- Implement character rotation logic
-- Handle both position and node targets
-- Test smooth rotation animations
-
-## Phase 3: Advanced Features
-
-### 3.1 Parallel Execution System
-- Implement parallel action group management
-- Add completion tracking for parallel actions
-- Test with multiple simultaneous actions
-
-### 3.2 DialogueAction
-- Implement basic dialogue display
-- Add duration-based completion
-- Test with different text lengths
-
-### 3.3 AnimationAction
-- Implement animation playback
-- Handle AnimationPlayer integration
-- Test with looping and non-looping animations
-
-## Phase 4: Completion and Callback System
-
-### 4.1 Signal Integration
-- Connect action signals to CutsceneManager
-- Implement action completion handling
-- Add error handling and failure recovery
-
-### 4.2 Callback System
-- Implement action-level callbacks
-- Add cutscene-level callbacks
-- Test callback chaining
-
-### 4.3 State Management
-- Implement full state transitions
-- Add pause/resume functionality
-- Test state persistence
-
-## Phase 5: Testing and Examples
-
-### 5.1 Basic Cutscene Example
-- Create simple sequential cutscene
-- Test all core action types
-- Validate completion flow
-
-### 5.2 Complex Cutscene Example
-- Implement the requirement example:
- - Character1 moves to 234, 591
- - Character2 moves to 912, 235 (simultaneously)
- - Character2 turns to Character1
- - Character2 says dialogue
- - Character1 plays shocked animation
-- Test parallel execution
-- Validate timing and synchronization
-
-### 5.3 Edge Case Testing
-- Test empty action queues
-- Test failed actions
-- Test rapid state changes
-- Test memory management
-
-## Phase 6: Extensibility Features
-
-### 6.1 Custom Action Example
-- Create a custom action type
-- Demonstrate extension process
-- Validate compatibility with core system
-
-### 6.2 Plugin Architecture
-- Implement basic plugin system
-- Create example plugin
-- Test plugin integration
-
-### 6.3 Performance Optimization
-- Implement object pooling
-- Add performance monitoring
-- Optimize update loops
-
-## Implementation Order
-
-```mermaid
-graph TD
- A[Phase 1: Foundation] --> B[Phase 2: Core Actions]
- B --> C[Phase 3: Advanced Features]
- C --> D[Phase 4: Completion System]
- D --> E[Phase 5: Testing]
- E --> F[Phase 6: Extensibility]
-
- A1[Action Base Class] --> A2[CutsceneManager Core]
- A --> A1
- A --> A2
-
- B1[WaitAction] --> B2[MoveAction]
- B2 --> B3[TurnAction]
- B --> B1
- B --> B2
- B --> B3
-
- C1[Parallel Execution] --> C2[DialogueAction]
- C2 --> C3[AnimationAction]
- C --> C1
- C --> C2
- C --> C3
-
- D1[Signal Integration] --> D2[Callback System]
- D2 --> D3[State Management]
- D --> D1
- D --> D2
- D --> D3
-
- E1[Basic Example] --> E2[Complex Example]
- E2 --> E3[Edge Case Testing]
- E --> E1
- E --> E2
- E --> E3
-
- F1[Custom Action] --> F2[Plugin Architecture]
- F2 --> F3[Performance Optimization]
- F --> F1
- F --> F2
- F --> F3
-```
-
-## Testing Strategy
-
-### Unit Tests
-- Each action type tested independently
-- CutsceneManager state transitions
-- Signal emission and connection
-- Error handling paths
-
-### Integration Tests
-- Sequential action execution
-- Parallel action execution
-- Mixed sequential/parallel sequences
-- Callback chaining
-
-### Performance Tests
-- Large action queues
-- Multiple simultaneous cutscenes
-- Memory allocation/garbage collection
-- Frame rate impact
-
-## File Structure Implementation
-
-```
-res://
-└── cutscene/
- ├── CutsceneManager.gd
- ├── Action.gd
- ├── actions/
- │ ├── MoveAction.gd
- │ ├── TurnAction.gd
- │ ├── DialogueAction.gd
- │ ├── AnimationAction.gd
- │ └── WaitAction.gd
- ├── examples/
- │ ├── basic_cutscene.tscn
- │ ├── complex_cutscene.tscn
- │ └── custom_action_example.gd
- └── tests/
- ├── test_action.gd
- ├── test_cutscene_manager.gd
- └── test_parallel_execution.gd
-```
-
-## Success Criteria
-
-### Functional Requirements
-- [ ] Sequential actions execute in order
-- [ ] Parallel actions execute simultaneously
-- [ ] All core action types function correctly
-- [ ] Completion system works reliably
-- [ ] Error handling is robust
-
-### Performance Requirements
-- [ ] Frame rate remains stable during cutscenes
-- [ ] Memory usage is optimized
-- [ ] No significant garbage collection spikes
-
-### Extensibility Requirements
-- [ ] New action types can be added easily
-- [ ] System can be extended with plugins
-- [ ] Custom callbacks work as expected
-
-## Timeline Estimate
-
-### Phase 1: Foundation - 2 days
-### Phase 2: Core Action Types - 3 days
-### Phase 3: Advanced Features - 2 days
-### Phase 4: Completion and Callback System - 2 days
-### Phase 5: Testing and Examples - 3 days
-### Phase 6: Extensibility Features - 2 days
-
-**Total Estimated Time: 14 days**
-
-This roadmap provides a structured approach to implementing the cutscene system, ensuring that each component is properly tested and integrated before moving on to the next phase.
\ No newline at end of file
diff --git a/project.godot b/project.godot
index ce71489..8f2650c 100644
--- a/project.godot
+++ b/project.godot
@@ -14,3 +14,7 @@ config/name="adventure-ai"
run/main_scene="res://cutscene/examples/sample_cutscene.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"
+
+[editor_plugins]
+
+enabled=PackedStringArray("res://addons/cutscene_editor/plugin.cfg")
diff --git a/sample_cutscene_example.md b/sample_cutscene_example.md
deleted file mode 100644
index dba379c..0000000
--- a/sample_cutscene_example.md
+++ /dev/null
@@ -1,242 +0,0 @@
-# Sample Cutscene Example
-
-## Overview
-This document provides a complete example of how to use the cutscene system to create a complex sequence as described in the requirements.
-
-## Example Scenario
-The example implements this sequence:
-1. Character1 moves to position (234, 591)
-2. At the same time, Character2 moves to position (912, 235)
-3. When both arrive, Character2 turns to face Character1
-4. Character2 says a dialogue line to Character1
-5. A special animation (shocked) is played for Character1
-
-## Sample Implementation
-
-```gdscript
-# Sample cutscene implementation
-extends Node2D
-
-# Character nodes (would be set in the editor or loaded)
-var character1: Node2D
-var character2: Node2D
-
-# Cutscene manager
-var cutscene_manager: CutsceneManager
-
-func _ready() -> void:
- # Initialize the cutscene system
- setup_cutscene()
-
- # Start the cutscene
- cutscene_manager.start()
-
-func setup_cutscene() -> void:
- # Create the cutscene manager
- cutscene_manager = CutsceneManager.new()
- add_child(cutscene_manager)
-
- # Create the action sequence as described in requirements
-
- # 1. & 2. Characters move simultaneously
- var parallel_moves = [
- MoveAction.new(character1, Vector2(234, 591), 150.0), # Character1 moves
- MoveAction.new(character2, Vector2(912, 235), 150.0) # Character2 moves
- ]
- cutscene_manager.add_parallel_actions(parallel_moves)
-
- # 3. Character2 turns to face Character1
- var turn_action = TurnAction.new(character2, character1, 3.0)
- cutscene_manager.add_action(turn_action)
-
- # 4. Character2 says dialogue
- var dialogue_action = DialogueAction.new(character2, "Hello there, friend!", 2.0)
- cutscene_manager.add_action(dialogue_action)
-
- # 5. Character1 plays shocked animation
- var animation_action = AnimationAction.new(character1, "shocked", false)
- cutscene_manager.add_action(animation_action)
-
- # Set up cutscene callbacks
- cutscene_manager.on_completed = func():
- print("Cutscene completed!")
- # Return to gameplay or next scene
-
-# Alternative implementation with more complex chaining
-func setup_advanced_cutscene() -> void:
- cutscene_manager = CutsceneManager.new()
- add_child(cutscene_manager)
-
- # Create actions
- var move1 = MoveAction.new(character1, Vector2(234, 591), 150.0)
- var move2 = MoveAction.new(character2, Vector2(912, 235), 150.0)
- var turn = TurnAction.new(character2, character1, 3.0)
- var dialogue = DialogueAction.new(character2, "Hello there, friend!", 2.0)
- var animation = AnimationAction.new(character1, "shocked", false)
-
- # Add parallel actions
- cutscene_manager.add_parallel_actions([move1, move2])
-
- # Add sequential actions
- cutscene_manager.add_action(turn)
- cutscene_manager.add_action(dialogue)
- cutscene_manager.add_action(animation)
-
- # Set completion callback
- cutscene_manager.on_completed = _on_cutscene_finished
-}
-
-func _on_cutscene_finished() -> void:
- print("The cutscene has finished playing")
- # Handle post-cutscene logic here
- # For example, transition to gameplay state
-```
-
-## More Complex Example with Conditional Logic
-
-```gdscript
-# Advanced example with conditional actions
-func setup_conditional_cutscene() -> void:
- cutscene_manager = CutsceneManager.new()
- add_child(cutscene_manager)
-
- # Move characters into position
- var move_group = [
- MoveAction.new(character1, Vector2(300, 400)),
- MoveAction.new(character2, Vector2(500, 400))
- ]
- cutscene_manager.add_parallel_actions(move_group)
-
- # Conditional dialogue based on game state
- var dialogue_action = DialogueAction.new(
- character1,
- get_dialogue_text(),
- 3.0
- )
- cutscene_manager.add_action(dialogue_action)
-
- # Play different animation based on dialogue response
- var animation_action = AnimationAction.new(
- character2,
- get_reaction_animation(),
- false
- )
- cutscene_manager.add_action(animation_action)
-
- # Wait for a moment
- cutscene_manager.add_action(WaitAction.new(1.0))
-
- # Final action
- var final_dialogue = DialogueAction.new(
- character2,
- "That was interesting!",
- 2.0
- )
- cutscene_manager.add_action(final_dialogue)
-
-func get_dialogue_text() -> String:
- # This could be based on game state, player choices, etc.
- return "What do you think about this situation?"
-
-func get_reaction_animation() -> String:
- # This could be based on game state, player choices, etc.
- return "thoughtful"
-```
-
-## Integration with Game Systems
-
-```gdscript
-# Example of integrating with a game state manager
-class_name GameCutsceneManager
-extends Node
-
-var cutscene_manager: CutsceneManager
-var game_state: GameStateManager
-
-func play_story_cutscene(story_key: String) -> void:
- # Pause gameplay
- game_state.set_state(GameState.CUTSCENE)
-
- # Create cutscene based on story key
- var cutscene_data = load_cutscene_data(story_key)
- cutscene_manager = create_cutscene_from_data(cutscene_data)
-
- # Set up completion callback to resume gameplay
- cutscene_manager.on_completed = func():
- game_state.set_state(GameState.PLAYING)
- # Clean up
- cutscene_manager.queue_free()
- cutscene_manager = null
-
- # Start the cutscene
- cutscene_manager.start()
-
-func load_cutscene_data(key: String) -> Dictionary:
- # Load cutscene data from a file or database
- # This could be JSON, CSV, or custom format
- return {
- "actions": [
- {"type": "move", "character": "player", "position": [100, 200]},
- {"type": "dialogue", "character": "npc", "text": "Hello!"},
- {"type": "animation", "character": "player", "name": "wave"}
- ]
- }
-
-func create_cutscene_from_data(data: Dictionary) -> CutsceneManager:
- var manager = CutsceneManager.new()
- add_child(manager)
-
- # Convert data to actions
- for action_data in data["actions"]:
- var action = create_action_from_data(action_data)
- if action:
- manager.add_action(action)
-
- return manager
-
-func create_action_from_data(data: Dictionary) -> Action:
- match data["type"]:
- "move":
- var character = get_character_by_name(data["character"])
- var position = Vector2(data["position"][0], data["position"][1])
- return MoveAction.new(character, position)
- "dialogue":
- var character = get_character_by_name(data["character"])
- return DialogueAction.new(character, data["text"])
- "animation":
- var character = get_character_by_name(data["character"])
- return AnimationAction.new(character, data["name"])
- _:
- return null
-```
-
-## Performance Considerations
-
-```gdscript
-# Example of optimizing cutscene performance
-func setup_optimized_cutscene() -> void:
- cutscene_manager = CutsceneManager.new()
- add_child(cutscene_manager)
-
- # Pre-load any resources needed for actions
- preload_animations()
- preload_dialogue_textures()
-
- # Use object pooling for frequently created actions
- var move_action_pool = []
- for i in range(10):
- move_action_pool.append(MoveAction.new(null, Vector2.ZERO))
-
- # Reuse actions when possible
- var action1 = get_pooled_move_action(character1, Vector2(100, 100))
- var action2 = get_pooled_move_action(character2, Vector2(200, 200))
-
- cutscene_manager.add_parallel_actions([action1, action2])
-
- # Clean up when done
- cutscene_manager.on_completed = func():
- release_pooled_actions()
- # Other cleanup
-```
-
-This sample demonstrates how to use the cutscene system to create complex, dynamic sequences that can handle both sequential and parallel actions while maintaining clean, readable code.
\ No newline at end of file