From 1938bf2e5bb5a7a8f6249e5b6b6815c2026d5a6c Mon Sep 17 00:00:00 2001 From: Prantik-07 Date: Fri, 30 Jan 2026 18:00:38 +0530 Subject: [PATCH 1/2] feat: Add sink availability indicator with blinking light - Add available export variable to sink.gd - Add IndicatorLight ColorRect to sink.tscn - Implement blinking animation logic - Light shows only when sink is available Fixes #65 Signed-off-by: Prantik --- Scenes/sink.tscn | 8 ++++++++ Scripts/sink.gd | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Scenes/sink.tscn b/Scenes/sink.tscn index 28c867d..25c9a1d 100644 --- a/Scenes/sink.tscn +++ b/Scenes/sink.tscn @@ -20,4 +20,12 @@ script = ExtResource("2_16xt0") position = Vector2(0, -5.83331) shape = SubResource("RectangleShape2D_7l3ci") +[node name="IndicatorLight" type="ColorRect" parent="."] +offset_left = -10.0 +offset_top = -220.0 +offset_right = 10.0 +offset_bottom = -200.0 +color = Color(0, 1, 0, 1) +z_index = 1 + [connection signal="area_entered" from="Area2D" to="Area2D" method="_on_area_entered"] diff --git a/Scripts/sink.gd b/Scripts/sink.gd index 4939319..004da22 100644 --- a/Scripts/sink.gd +++ b/Scripts/sink.gd @@ -1,13 +1,27 @@ extends Sprite2D @export var expectedType: String +@export var available: bool = true + +var blink_timer: float = 0.0 +var blink_interval: float = 0.5 +var indicator_light: ColorRect = null # Called when the node enters the scene tree for the first time. func _ready() -> void: - pass # Replace with function body. + indicator_light = get_node_or_null("IndicatorLight") + if indicator_light == null: + push_warning("IndicatorLight not found in sink scene") # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: - pass + if indicator_light != null: + if available: + blink_timer += delta + if blink_timer >= blink_interval: + blink_timer = 0.0 + indicator_light.visible = !indicator_light.visible + else: + indicator_light.visible = false From d92a62d7352e04cf5450cbdbfe91998b274808bb Mon Sep 17 00:00:00 2001 From: Prantik-07 Date: Sun, 1 Feb 2026 04:53:43 +0530 Subject: [PATCH 2/2] Add smart failure feedback with educational messages Fixes #79 --- Scenes/message_display.tscn | 53 ++++++++++++++++++++++++++----- Scripts/TutorialManager.gd | 52 ++++++++++++++++++++++++++++++ Scripts/TutorialTooltip.gd | 20 ++++++++++++ Scripts/level.gd | 39 +++++++++++++++++++++-- Scripts/message_display.gd | 9 +++++- tutorial_data/basicEventFlow.json | 21 ++++++++++++ tutorial_data/boxClick.json | 25 +++++++++++++++ tutorial_data/dlqPattern.json | 21 ++++++++++++ tutorial_data/multiSink.json | 21 ++++++++++++ 9 files changed, 249 insertions(+), 12 deletions(-) create mode 100644 Scripts/TutorialManager.gd create mode 100644 Scripts/TutorialTooltip.gd create mode 100644 tutorial_data/basicEventFlow.json create mode 100644 tutorial_data/boxClick.json create mode 100644 tutorial_data/dlqPattern.json create mode 100644 tutorial_data/multiSink.json diff --git a/Scenes/message_display.tscn b/Scenes/message_display.tscn index aaca9ee..b1f5dd0 100644 --- a/Scenes/message_display.tscn +++ b/Scenes/message_display.tscn @@ -11,13 +11,50 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_y0tj3") -[node name="Label" type="Label" parent="."] -layout_mode = 0 -offset_right = 1153.0 -offset_bottom = 649.0 +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.7) + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Panel" type="Panel" parent="CenterContainer"] +custom_minimum_size = Vector2(900, 500) +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/Panel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 40 +theme_override_constants/margin_top = 40 +theme_override_constants/margin_right = 40 +theme_override_constants/margin_bottom = 40 + +[node name="ScrollContainer" type="ScrollContainer" parent="CenterContainer/Panel/MarginContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/Panel/MarginContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 20 + +[node name="Label" type="Label" parent="CenterContainer/Panel/MarginContainer/ScrollContainer/VBoxContainer"] +layout_mode = 2 theme_override_colors/font_color = Color(0, 0.286275, 0.556863, 1) -theme_override_colors/font_shadow_color = Color(1, 0.94902, 0.870588, 1) -theme_override_font_sizes/font_size = 100 +theme_override_font_sizes/font_size = 28 horizontal_alignment = 1 -vertical_alignment = 1 -autowrap_mode = 2 +autowrap_mode = 3 diff --git a/Scripts/TutorialManager.gd b/Scripts/TutorialManager.gd new file mode 100644 index 0000000..538a184 --- /dev/null +++ b/Scripts/TutorialManager.gd @@ -0,0 +1,52 @@ +extends Node + +var current_tutorial: Dictionary = {} +var tutorials_completed: Dictionary = {} +var show_tutorials: bool = true +var current_step: int = 0 + +signal tutorial_started(level: String) +signal tutorial_step_completed(step: String) +signal tutorial_finished(level: String) + +func start_level_tutorial(level_name: String): + if tutorials_completed.get(level_name, false): + return + + var tutorial_path = "res://tutorial_data/" + level_name + ".json" + var tutorial_file = FileAccess.open(tutorial_path, FileAccess.READ) + if tutorial_file: + var tutorial_text = tutorial_file.get_as_text() + current_tutorial = JSON.parse_string(tutorial_text) + current_step = 0 + emit_signal("tutorial_started", level_name) + show_current_step() + +func show_current_step(): + if current_step >= current_tutorial.steps.size(): + finish_tutorial() + return + + var step = current_tutorial.steps[current_step] + show_tooltip(step.text, step.get("highlight", "")) + +func show_tooltip(text: String, highlight_target: String = ""): + var tooltip = preload("res://Scenes/TutorialTooltip.tscn").instantiate() + tooltip.set_text(text) + if highlight_target != "": + var target = get_tree().get_first_node_in_group(highlight_target) + if target: + tooltip.point_to(target) + add_child(tooltip) + +func next_step(): + current_step += 1 + show_current_step() + +func skip_tutorial(): + finish_tutorial() + +func finish_tutorial(): + tutorials_completed[current_tutorial.level] = true + emit_signal("tutorial_finished", current_tutorial.level) + current_tutorial = {} diff --git a/Scripts/TutorialTooltip.gd b/Scripts/TutorialTooltip.gd new file mode 100644 index 0000000..826cccd --- /dev/null +++ b/Scripts/TutorialTooltip.gd @@ -0,0 +1,20 @@ +extends Control + +@onready var label = $Panel/MarginContainer/VBoxContainer/Label +@onready var next_button = $Panel/MarginContainer/VBoxContainer/HBoxContainer/NextButton +@onready var skip_button = $Panel/MarginContainer/VBoxContainer/HBoxContainer/SkipButton + +func set_text(text: String): + label.text = text + +func point_to(target: Node2D): + # Position tooltip near target + global_position = target.global_position + Vector2(50, -100) + +func _on_next_button_pressed(): + TutorialManager.next_step() + queue_free() + +func _on_skip_button_pressed(): + TutorialManager.skip_tutorial() + queue_free() diff --git a/Scripts/level.gd b/Scripts/level.gd index 19b15b6..7aa0bbd 100644 --- a/Scripts/level.gd +++ b/Scripts/level.gd @@ -45,7 +45,40 @@ func next_level(): print("End of Levels.") get_tree().change_scene_to_file("res://Scenes/end_of_all_levels.tscn") else: - print("Failed. Try Again") - message_display.show_message("Failed. Try Again") - await message_display.show_message_for_duration(2.0) + var failure_data = analyze_failure() + show_educational_failure(failure_data, message_display) + await message_display.show_message_for_duration(4.0) message_display.visible = false + +func analyze_failure() -> Dictionary: + var reason = "" + var hint = "" + var lesson = "" + + if not sinkUsed: + reason = "No events were delivered to any sink" + hint = "Click an event to select it, then click a sink to route it" + lesson = "In Knative, events must be routed from sources to sinks through triggers" + + elif sinkBoxMatchNeeded[levelind] and not sinkBoxMatchPresent: + reason = "Wrong event type delivered to sink" + hint = "Use filters to match event colors with sink colors" + lesson = "Knative Triggers use filters to ensure events reach the correct subscribers based on event attributes" + + elif dlsRequired[levelind] and not dlsUsed: + reason = "Failed events were not routed to Dead Letter Sink" + hint = "Click the DLS to catch events that hit the blockage" + lesson = "Dead Letter Sinks prevent data loss by catching events that fail to process" + + return { + "reason": reason, + "hint": hint, + "lesson": lesson + } + +func show_educational_failure(data: Dictionary, message_display: Control): + message_display.show_failure_with_lesson( + data.reason, + data.hint, + data.lesson + ) diff --git a/Scripts/message_display.gd b/Scripts/message_display.gd index 0692202..912d782 100644 --- a/Scripts/message_display.gd +++ b/Scripts/message_display.gd @@ -2,7 +2,7 @@ extends Control func show_message(text): print("Message displayed") - $Label.text=text + $CenterContainer/Panel/MarginContainer/ScrollContainer/VBoxContainer/Label.text = text func show_message_for_duration(duration: float) -> void: var timer := Timer.new() @@ -12,3 +12,10 @@ func show_message_for_duration(duration: float) -> void: timer.start() await timer.timeout timer.queue_free() + +func show_failure_with_lesson(reason: String, hint: String, lesson: String): + var full_message = "❌ Level Failed\n\n" + full_message += "Why: " + reason + "\n\n" + full_message += "💡 Hint: " + hint + "\n\n" + full_message += "🎓 Knative Concept:\n" + lesson + $CenterContainer/Panel/MarginContainer/ScrollContainer/VBoxContainer/Label.text = full_message diff --git a/tutorial_data/basicEventFlow.json b/tutorial_data/basicEventFlow.json new file mode 100644 index 0000000..3bd156d --- /dev/null +++ b/tutorial_data/basicEventFlow.json @@ -0,0 +1,21 @@ +{ + "level": "basicEventFlow", + "steps": [ + { + "text": "Welcome! In Knative, events flow from Sources to Sinks through Brokers.", + "highlight": "" + }, + { + "text": "Click this event to select it", + "highlight": "EventBox" + }, + { + "text": "Now click the sink to route the event", + "highlight": "Sink" + }, + { + "text": "Great! You just created a basic Knative event flow. This is like a Trigger connecting a Broker to a Sink.", + "highlight": "" + } + ] +} diff --git a/tutorial_data/boxClick.json b/tutorial_data/boxClick.json new file mode 100644 index 0000000..cbf4a3b --- /dev/null +++ b/tutorial_data/boxClick.json @@ -0,0 +1,25 @@ +{ + "level": "boxClick", + "steps": [ + { + "text": "Level 2: Triggers & Filters. In Knative, Triggers use filters to route events based on their attributes.", + "highlight": "" + }, + { + "text": "Notice the colored events. Each has a different 'type' attribute.", + "highlight": "EventBox" + }, + { + "text": "Drag a filter onto the conveyor to filter events by color", + "highlight": "Filter" + }, + { + "text": "Route matching events to the correct colored sink", + "highlight": "Sink" + }, + { + "text": "Perfect! Knative Triggers work the same way - filtering events by attributes like type, source, or custom fields.", + "highlight": "" + } + ] +} \ No newline at end of file diff --git a/tutorial_data/dlqPattern.json b/tutorial_data/dlqPattern.json new file mode 100644 index 0000000..a025cba --- /dev/null +++ b/tutorial_data/dlqPattern.json @@ -0,0 +1,21 @@ +{ + "level": "dlqPattern", + "steps": [ + { + "text": "Level 4: Dead Letter Queue. When events fail to process, they need somewhere to go.", + "highlight": "" + }, + { + "text": "Notice the blockage - events will fail here", + "highlight": "Blockage" + }, + { + "text": "Click the Dead Letter Sink (DLS) to route failed events there", + "highlight": "DLS" + }, + { + "text": "Perfect! In Knative, Dead Letter Sinks prevent data loss by catching failed events for debugging or retry.", + "highlight": "" + } + ] +} \ No newline at end of file diff --git a/tutorial_data/multiSink.json b/tutorial_data/multiSink.json new file mode 100644 index 0000000..66c5d27 --- /dev/null +++ b/tutorial_data/multiSink.json @@ -0,0 +1,21 @@ +{ + "level": "multiSink", + "steps": [ + { + "text": "Level 3: Multiple Sinks. In Knative, one Broker can route events to multiple subscribers.", + "highlight": "" + }, + { + "text": "You have multiple sinks. Each needs matching events.", + "highlight": "Sink" + }, + { + "text": "Use filters to route each event type to its matching sink", + "highlight": "Filter" + }, + { + "text": "Excellent! This is how Knative enables fan-out patterns - one event source, multiple subscribers.", + "highlight": "" + } + ] +} \ No newline at end of file