@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()