diff --git a/Scenes/ConceptGlossary.tscn b/Scenes/ConceptGlossary.tscn new file mode 100644 index 0000000..d189832 --- /dev/null +++ b/Scenes/ConceptGlossary.tscn @@ -0,0 +1,105 @@ +[gd_scene load_steps=2 format=3 uid="uid://concept_glossary"] + +[ext_resource type="Script" path="res://Scripts/ConceptGlossary.gd" id="1"] + +[node name="ConceptGlossary" type="Control"] +visible = false +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] +layer = 100 +visible = false + +[node name="Background" type="ColorRect" parent="CanvasLayer"] +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="Panel" type="Panel" parent="CanvasLayer"] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -400.0 +offset_top = -300.0 +offset_right = 400.0 +offset_bottom = 300.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/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 = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/Panel/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Title" type="Label" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "📖 Knative Concepts" +horizontal_alignment = 1 +theme_override_font_sizes/font_size = 24 + +[node name="SearchBox" type="LineEdit" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Search concepts..." + +[node name="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ResultsList" type="ItemList" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ConceptPanel" type="VBoxContainer" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="ConceptName" type="Label" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel"] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +horizontal_alignment = 1 + +[node name="Definition" type="Label" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="GameExample" type="Label" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="KnativeExample" type="Label" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="DocsButton" type="Button" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel"] +layout_mode = 2 +text = "📚 Read Official Docs" + +[node name="CloseButton" type="Button" parent="CanvasLayer/Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Close" + +[connection signal="item_selected" from="CanvasLayer/Panel/MarginContainer/VBoxContainer/ScrollContainer/ResultsList" to="." method="_on_results_list_item_selected"] diff --git a/Scenes/ConnectorLine.tscn b/Scenes/ConnectorLine.tscn new file mode 100644 index 0000000..1bfd21b --- /dev/null +++ b/Scenes/ConnectorLine.tscn @@ -0,0 +1,15 @@ +[gd_scene format=3 uid="uid://connectorline"] + +[ext_resource type="Script" path="res://Scripts/ConnectorLine.gd" id="1_script"] +[ext_resource type="Shader" path="res://Scripts/scrolling_line.gdshader" id="2_shader"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_1"] +shader = ExtResource("2_shader") +shader_parameter/speed = 1.0 + +[node name="ConnectorLine" type="Line2D"] +material = SubResource("ShaderMaterial_1") +width = 10.0 +default_color = Color(0, 0.6, 1, 0.5) +texture_mode = 1 +script = ExtResource("1_script") diff --git a/Scenes/basicEventFlow.tscn b/Scenes/basicEventFlow.tscn index 8a9a1cc..5dceb58 100644 --- a/Scenes/basicEventFlow.tscn +++ b/Scenes/basicEventFlow.tscn @@ -56,16 +56,8 @@ script = ExtResource("6_xyb5q") layout_mode = 0 offset_right = 342.0 offset_bottom = 132.0 - -[node name="RichTextLabel" type="RichTextLabel" parent="Control/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]START" +theme_override_font_sizes/font_size = 50 +text = "START" [node name="Control2" type="Control" parent="."] layout_mode = 3 @@ -81,16 +73,8 @@ script = ExtResource("7_3f2sq") layout_mode = 0 offset_right = 342.0 offset_bottom = 132.0 - -[node name="RichTextLabel" type="RichTextLabel" parent="Control2/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]RESTART" +theme_override_font_sizes/font_size = 50 +text = "RESTART" [connection signal="pressed" from="Control/Button" to="Control" method="_on_button_pressed"] [connection signal="pressed" from="Control2/Button" to="Control2" method="_on_button_pressed"] diff --git a/Scenes/boxClick.tscn b/Scenes/boxClick.tscn index 8516e59..94cdf79 100644 --- a/Scenes/boxClick.tscn +++ b/Scenes/boxClick.tscn @@ -74,16 +74,8 @@ script = ExtResource("9_va8kg") layout_mode = 0 offset_right = 342.0 offset_bottom = 132.0 - -[node name="RichTextLabel" type="RichTextLabel" parent="Control/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]START" +theme_override_font_sizes/font_size = 50 +text = "START" [node name="Control2" type="Control" parent="."] layout_mode = 3 @@ -98,16 +90,8 @@ script = ExtResource("10_rwca5") layout_mode = 0 offset_right = 342.0 offset_bottom = 132.0 - -[node name="RichTextLabel" type="RichTextLabel" parent="Control2/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]RESTART" +theme_override_font_sizes/font_size = 50 +text = "RESTART" [node name="Draggable Filter" type="Area2D" parent="."] position = Vector2(288.333, 205) diff --git a/Scenes/box_b.tscn b/Scenes/box_b.tscn index 1498ff0..3a3e4f3 100644 --- a/Scenes/box_b.tscn +++ b/Scenes/box_b.tscn @@ -1,12 +1,14 @@ +[gd_scene format=3 uid="uid://cg1qlr4r42xs6"] [gd_scene load_steps=6 format=3 uid="uid://cg1qlr4r42xs6"] [ext_resource type="Texture2D" uid="uid://cjn14twvcwa8y" path="res://2D Assets/boxes/blueBox.png" id="1_mjw0e"] -[ext_resource type="Script" path="res://Scripts/event_box.gd" id="2_3md8f"] -[ext_resource type="Script" path="res://Scripts/event_box_clickable.gd" id="2_raetl"] +[ext_resource type="Script" uid="uid://dytl3q2sat4ew" path="res://Scripts/event_box.gd" id="2_3md8f"] +[ext_resource type="Script" uid="uid://5ydkp5vyru6l" path="res://Scripts/event_box_clickable.gd" id="2_raetl"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_6uvdh"] size = Vector2(211.428, 211.429) +[node name="BoxB" type="Sprite2D" unique_id=1375488351 groups=["Box"]] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5pura"] content_margin_top = 9.0 bg_color = Color(0.431373, 0.564706, 1, 1) @@ -21,10 +23,10 @@ texture = ExtResource("1_mjw0e") script = ExtResource("2_3md8f") boxType = "Blue" -[node name="Area2D" type="Area2D" parent="." groups=["Box"]] +[node name="Area2D" type="Area2D" parent="." unique_id=1970047476 groups=["Box"]] script = ExtResource("2_raetl") -[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D" unique_id=650521939] position = Vector2(0.000366211, 0) scale = Vector2(0.7, 0.7) shape = SubResource("RectangleShape2D_6uvdh") diff --git a/Scenes/dlqPattern.tscn b/Scenes/dlqPattern.tscn index 52acb18..11af722 100644 --- a/Scenes/dlqPattern.tscn +++ b/Scenes/dlqPattern.tscn @@ -72,20 +72,10 @@ script = ExtResource("11_fwn7q") [node name="Button" type="Button" parent="Control"] layout_mode = 0 offset_top = 1.0 -offset_right = 151.0 -offset_bottom = 65.0 - -[node name="RichTextLabel2" type="RichTextLabel" parent="Control/Button"] -layout_mode = 0 -offset_left = -10.0 -offset_top = 16.0 -offset_right = 328.0 -offset_bottom = 89.0 -scale = Vector2(0.5, 0.5) -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]START" +offset_right = 200.0 +offset_bottom = 80.0 +theme_override_font_sizes/font_size = 30 +text = "START" [node name="Control2" type="Control" parent="."] layout_mode = 3 @@ -98,19 +88,10 @@ script = ExtResource("11_pd3ou") [node name="Button" type="Button" parent="Control2"] layout_mode = 0 -offset_right = 342.0 -offset_bottom = 132.0 -scale = Vector2(0.45, 0.45) - -[node name="RichTextLabel" type="RichTextLabel" parent="Control2/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]RESTART" +offset_right = 200.0 +offset_bottom = 80.0 +theme_override_font_sizes/font_size = 30 +text = "RESTART" [node name="Blockage" parent="." instance=ExtResource("13_cjnva")] position = Vector2(516, 270) 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/Scenes/multiSink.tscn b/Scenes/multiSink.tscn index 0a6fbb5..bc44927 100644 --- a/Scenes/multiSink.tscn +++ b/Scenes/multiSink.tscn @@ -232,16 +232,8 @@ layout_mode = 0 offset_right = 342.0 offset_bottom = 132.0 scale = Vector2(0.9, 0.9) - -[node name="RichTextLabel" type="RichTextLabel" parent="Control/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]START" +theme_override_font_sizes/font_size = 50 +text = "START" [node name="Control2" type="Control" parent="."] layout_mode = 3 @@ -258,16 +250,8 @@ layout_mode = 0 offset_right = 342.0 offset_bottom = 132.0 scale = Vector2(0.9, 0.9) - -[node name="RichTextLabel" type="RichTextLabel" parent="Control2/Button"] -layout_mode = 0 -offset_top = 37.0 -offset_right = 338.0 -offset_bottom = 110.0 -mouse_filter = 2 -theme_override_font_sizes/normal_font_size = 50 -bbcode_enabled = true -text = "[center]RESTART" +theme_override_font_sizes/font_size = 50 +text = "RESTART" [connection signal="area_entered" from="Draggable Filter" to="Draggable Filter" method="_on_area_entered"] [connection signal="body_shape_exited" from="Draggable Filter" to="Draggable Filter" method="_on_body_shape_exited"] diff --git a/Scenes/sink.tscn b/Scenes/sink.tscn index fac64be..296734e 100644 --- a/Scenes/sink.tscn +++ b/Scenes/sink.tscn @@ -29,6 +29,13 @@ 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 [node name="hoverlabel" type="RichTextLabel" parent="."] visible = false offset_left = 180.0 diff --git a/Scripts/ConceptGlossary.gd b/Scripts/ConceptGlossary.gd new file mode 100644 index 0000000..2b33067 --- /dev/null +++ b/Scripts/ConceptGlossary.gd @@ -0,0 +1,140 @@ +extends Control + +@onready var search_box = $CanvasLayer/Panel/MarginContainer/VBoxContainer/SearchBox +@onready var results_list = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ScrollContainer/ResultsList +@onready var concept_panel = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel +@onready var concept_name = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel/ConceptName +@onready var definition = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel/Definition +@onready var game_example = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel/GameExample +@onready var knative_example = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel/KnativeExample +@onready var docs_button = $CanvasLayer/Panel/MarginContainer/VBoxContainer/ConceptPanel/DocsButton +@onready var close_button = $CanvasLayer/Panel/MarginContainer/VBoxContainer/CloseButton + +var concepts = { + "source": { + "name": "Event Source", + "definition": "A source generates events and sends them to a broker", + "game_example": "The event boxes at the start of each level", + "knative_example": "PingSource, ApiServerSource, KafkaSource", + "docs": "https://knative.dev/docs/eventing/sources/" + }, + "broker": { + "name": "Broker", + "definition": "A broker receives events from sources and delivers them to sinks via triggers", + "game_example": "The invisible routing system that moves events in the game", + "knative_example": "MTChannelBasedBroker, RabbitMQBroker", + "docs": "https://knative.dev/docs/eventing/brokers/" + }, + "trigger": { + "name": "Trigger", + "definition": "A trigger filters events and routes matching ones to a specific sink", + "game_example": "The connection you create from event to sink", + "knative_example": "Trigger with CloudEvents attribute filters", + "docs": "https://knative.dev/docs/eventing/triggers/" + }, + "filter": { + "name": "Filter", + "definition": "Rules that determine which events match a trigger", + "game_example": "The colored filters you drag onto the conveyor", + "knative_example": "CloudEvents attribute filters (type, source, etc.)", + "docs": "https://knative.dev/docs/eventing/triggers/#trigger-filtering" + }, + "sink": { + "name": "Sink", + "definition": "A sink receives and processes events", + "game_example": "The colored boxes where events are delivered", + "knative_example": "Knative Service, Channel, URI endpoint", + "docs": "https://knative.dev/docs/eventing/sinks/" + }, + "dlq": { + "name": "Dead Letter Queue", + "definition": "A special sink that catches events that fail to process", + "game_example": "The DLS that catches blocked events in Level 4", + "knative_example": "DeadLetterSink in delivery spec", + "docs": "https://knative.dev/docs/eventing/event-delivery/" + }, + "cloudevents": { + "name": "CloudEvents", + "definition": "A standard format for describing events", + "game_example": "The event boxes with different colors (types)", + "knative_example": "CloudEvents spec with type, source, id, etc.", + "docs": "https://cloudevents.io/" + }, + "channel": { + "name": "Channel", + "definition": "A messaging channel that can be used as a sink or source", + "game_example": "Not directly shown in current levels", + "knative_example": "InMemoryChannel, KafkaChannel", + "docs": "https://knative.dev/docs/eventing/channels/" + }, + "subscription": { + "name": "Subscription", + "definition": "Routes events from a channel to a sink", + "game_example": "Similar to the trigger concept in the game", + "knative_example": "Subscription connecting Channel to Sink", + "docs": "https://knative.dev/docs/eventing/channels/subscriptions/" + }, + "retry": { + "name": "Retry Policy", + "definition": "Configuration for retrying failed event deliveries", + "game_example": "Related to DLQ pattern in Level 4", + "knative_example": "Retry count and backoff delay in delivery spec", + "docs": "https://knative.dev/docs/eventing/event-delivery/" + } +} + +func _ready(): + add_to_group("Glossary") + search_box.text_changed.connect(_on_search_changed) + close_button.pressed.connect(_on_close_pressed) + docs_button.pressed.connect(_on_docs_pressed) + concept_panel.visible = false + populate_all_concepts() + +func populate_all_concepts(): + results_list.clear() + for key in concepts: + results_list.add_item(concepts[key].name) + +func _on_search_changed(query: String): + if query == "": + populate_all_concepts() + return + + results_list.clear() + for key in concepts: + if key.contains(query.to_lower()) or \ + concepts[key].name.to_lower().contains(query.to_lower()): + results_list.add_item(concepts[key].name) + +func _on_results_list_item_selected(index: int): + var item_text = results_list.get_item_text(index) + for key in concepts: + if concepts[key].name == item_text: + show_concept(key) + break + +func show_concept(concept_id: String): + var concept = concepts.get(concept_id) + if concept: + concept_name.text = concept.name + definition.text = concept.definition + game_example.text = "🎮 In game: " + concept.game_example + knative_example.text = "📦 In Knative: " + concept.knative_example + docs_button.set_meta("url", concept.docs) + concept_panel.visible = true + +func _on_close_pressed(): + $CanvasLayer.visible = false + +func _on_docs_pressed(): + var url = docs_button.get_meta("url") + OS.shell_open(url) + +func open_to_term(term: String): + $CanvasLayer.visible = true + if concepts.has(term): + show_concept(term) + +func open(): + $CanvasLayer.visible = true diff --git a/Scripts/ConceptGlossary.gd.uid b/Scripts/ConceptGlossary.gd.uid new file mode 100644 index 0000000..d7c98b0 --- /dev/null +++ b/Scripts/ConceptGlossary.gd.uid @@ -0,0 +1 @@ +uid://bkuqtb0brcw4t diff --git a/Scripts/ConnectorLine.gd b/Scripts/ConnectorLine.gd new file mode 100644 index 0000000..4ef2027 --- /dev/null +++ b/Scripts/ConnectorLine.gd @@ -0,0 +1,129 @@ +extends Line2D + +# Nodes to connect +var start_node: Node2D +var end_node: Node2D + +# Animation properties +var packet_timer = 0.0 +var spawn_interval = 0.5 # Spawn a packet every 0.5 seconds +var packet_speed = 200.0 # Pixels per second + +# Array to track active packets (sprites) +var packets = [] +var active = true + +func setup(start: Node2D, end: Node2D): + start_node = start + end_node = end + + # Set line style + width = 3.0 + default_color = Color(1.0, 0.2, 0.2, 0.8) # Vibrant Red + texture_mode = Line2D.LINE_TEXTURE_TILE + _create_dotted_texture() + + _update_line_positions() + +func _ready(): + texture_repeat = CanvasItem.TEXTURE_REPEAT_ENABLED + begin_cap_mode = Line2D.LINE_CAP_ROUND + end_cap_mode = Line2D.LINE_CAP_ROUND + +func fade_out(): + active = false + var tween = create_tween() + # Clear all packets immediately + for p in packets: + if is_instance_valid(p.node): + p.node.queue_free() + packets.clear() + # Fade out the line + tween.tween_property(self, "modulate:a", 0.0, 0.3) + tween.finished.connect(queue_free) + +func _process(delta): + if not active: return + + # Update connection line in case objects move + _update_line_positions() + + # Spawn packets + packet_timer += delta + if packet_timer >= spawn_interval: + packet_timer = 0.0 + _spawn_packet() + + # Move packets + _move_packets(delta) + +func _update_line_positions(): + if is_instance_valid(start_node) and is_instance_valid(end_node): + clear_points() + # Start at bottom of box, End at top of sink + # Small adjustment to Vector2(0, 0) logic might be needed for "middle corner" + var start_p = start_node.global_position + Vector2(0, 70) + var end_p = end_node.global_position - Vector2(0, 110) + add_point(start_p) + add_point(end_p) + else: + queue_free() + +func _spawn_packet(): + if not is_instance_valid(start_node) or not is_instance_valid(end_node): return + + # Create a visual arrow using a Label + var pkt = Label.new() + pkt.text = "v" # Arrow pointing down + pkt.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + + # High-contrast Red Theme + pkt.add_theme_color_override("font_color", Color(1.0, 0.3, 0.3)) # Bright Red + pkt.add_theme_color_override("font_shadow_color", Color(0.5, 0, 0, 1.0)) + pkt.add_theme_constant_override("shadow_offset_x", 0) + pkt.add_theme_constant_override("shadow_offset_y", 0) + pkt.add_theme_constant_override("shadow_outline_size", 14) + pkt.add_theme_font_size_override("font_size", 30) + + # Use the same points as the line + var start_pos = start_node.global_position + Vector2(0, 70) + var end_pos = end_node.global_position - Vector2(0, 110) + + pkt.global_position = start_pos - Vector2(10, 15) # Offset to center + add_child(pkt) + + packets.append({ + "node": pkt, + "progress": 0.0, + "start": start_pos, + "end": end_pos + }) + +func _move_packets(delta): + # Iterate backwards to allow removing items + for i in range(packets.size() - 1, -1, -1): + var p = packets[i] + + var total_dist = p.start.distance_to(p.end) + if total_dist == 0: continue + + p.progress += (packet_speed * delta) / total_dist + + if p.progress >= 1.0: + p.node.queue_free() + packets.remove_at(i) + else: + var current_pos = p.start.lerp(p.end, p.progress) + p.node.global_position = current_pos - Vector2(10, 15) + +func _create_dotted_texture(): + var img = Image.create(16, 16, false, Image.FORMAT_RGBA8) + img.fill(Color(0,0,0,0)) # Transparent + + # Draw a small 4x4 dot in the middle + for x in range(6, 10): + for y in range(6, 10): + img.set_pixel(x, y, Color(1, 1, 1, 1)) + + var tex = ImageTexture.create_from_image(img) + texture = tex diff --git a/Scripts/ConnectorLine.gd.uid b/Scripts/ConnectorLine.gd.uid new file mode 100644 index 0000000..cdb8089 --- /dev/null +++ b/Scripts/ConnectorLine.gd.uid @@ -0,0 +1 @@ +uid://yl8kaawhc6m4 diff --git a/Scripts/ConveyerController.gd b/Scripts/ConveyerController.gd index e7c6fc0..62e3f89 100644 --- a/Scripts/ConveyerController.gd +++ b/Scripts/ConveyerController.gd @@ -11,12 +11,12 @@ var can_send = false var started = false func initialise(): - self.selected + self.selected = null self.events = [] self.destination = [] self.conveyer = [] self.conveyerInd = 0 - self.dragging + self.dragging = null self.sendingEnd = false self.can_send = false self.started = false @@ -41,6 +41,18 @@ func _process(delta: float) -> void: pass func create_conveyor(): + if conveyer.size() == 0: + print("ERROR: No conveyor Line2D found in scene!") + return + + # We use min() to ensure we don't go out of bounds of the actual Line2D nodes + # but we let conveyerInd grow so that destination and events indexing works. + var line_idx = min(conveyerInd, conveyer.size() - 1) + + conveyer[line_idx].set_point_position(0, selected.get_position()) + conveyer[line_idx].set_point_position(1, destination[conveyerInd]) + + conveyerInd += 1 conveyer[conveyerInd].set_point_position(0, selected.get_position()) conveyer[conveyerInd].set_point_position(1, destination[conveyerInd]) diff --git a/Scripts/ConveyerController.gd.uid b/Scripts/ConveyerController.gd.uid new file mode 100644 index 0000000..d8ac4c5 --- /dev/null +++ b/Scripts/ConveyerController.gd.uid @@ -0,0 +1 @@ +uid://bnlfxoacpufh8 diff --git a/Scripts/HoverLabel.gd b/Scripts/HoverLabel.gd new file mode 100644 index 0000000..d4d4eb5 --- /dev/null +++ b/Scripts/HoverLabel.gd @@ -0,0 +1,45 @@ +extends Label + +var hover_text: String = "" +var element_type: String = "" + +func _ready(): + mouse_entered.connect(_on_mouse_entered) + mouse_exited.connect(_on_mouse_exited) + mouse_filter = Control.MOUSE_FILTER_PASS + +func set_hover_info(type: String, description: String): + element_type = type + hover_text = description + +func _on_mouse_entered(): + if hover_text != "": + var tooltip = create_tooltip() + add_child(tooltip) + +func _on_mouse_exited(): + for child in get_children(): + if child.name == "HoverTooltip": + child.queue_free() + +func create_tooltip() -> Control: + var tooltip = Panel.new() + tooltip.name = "HoverTooltip" + tooltip.custom_minimum_size = Vector2(200, 60) + tooltip.position = Vector2(0, -70) + + var label = Label.new() + label.text = hover_text + label.autowrap_mode = TextServer.AUTOWRAP_WORD + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + + var margin = MarginContainer.new() + margin.add_theme_constant_override("margin_left", 10) + margin.add_theme_constant_override("margin_right", 10) + margin.add_theme_constant_override("margin_top", 10) + margin.add_theme_constant_override("margin_bottom", 10) + margin.add_child(label) + + tooltip.add_child(margin) + return tooltip diff --git a/Scripts/HoverLabel.gd.uid b/Scripts/HoverLabel.gd.uid new file mode 100644 index 0000000..6a713b1 --- /dev/null +++ b/Scripts/HoverLabel.gd.uid @@ -0,0 +1 @@ +uid://cbej48bod4f4a diff --git a/Scripts/SinkClick.gd b/Scripts/SinkClick.gd index 35de978..9a178d9 100644 --- a/Scripts/SinkClick.gd +++ b/Scripts/SinkClick.gd @@ -1,8 +1,10 @@ extends Area2D # Called when the node enters the scene tree for the first time. func _input_event(viewport, event, shape_idx): - if event.is_pressed() and ConveyerController.selected != null: - self.on_click() + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: + if ConveyerController.selected != null: + get_viewport().set_input_as_handled() + self.on_click() func on_click(): print("hey") diff --git a/Scripts/SinkClick.gd.uid b/Scripts/SinkClick.gd.uid new file mode 100644 index 0000000..626a000 --- /dev/null +++ b/Scripts/SinkClick.gd.uid @@ -0,0 +1 @@ +uid://w754cpbe067v diff --git a/Scripts/TutorialManager.gd b/Scripts/TutorialManager.gd new file mode 100644 index 0000000..2830107 --- /dev/null +++ b/Scripts/TutorialManager.gd @@ -0,0 +1,74 @@ +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 = ""): + if not ResourceLoader.exists("res://Scenes/TutorialTooltip.tscn"): + print("TutorialTooltip scene not found - skipping tutorial step") + return + + var tooltip_scene = load("res://Scenes/TutorialTooltip.tscn") + if tooltip_scene: + var tooltip = tooltip_scene.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 = {} + +func open_glossary(term: String = ""): + var glossary = get_tree().get_first_node_in_group("Glossary") + + if not glossary: + # Auto-instantiate if missing + var glossary_scene = load("res://Scenes/ConceptGlossary.tscn") + if glossary_scene: + glossary = glossary_scene.instantiate() + get_tree().root.add_child(glossary) + + if glossary: + if term != "": + glossary.open_to_term(term) + else: + glossary.open() diff --git a/Scripts/TutorialManager.gd.uid b/Scripts/TutorialManager.gd.uid new file mode 100644 index 0000000..c8a7ac3 --- /dev/null +++ b/Scripts/TutorialManager.gd.uid @@ -0,0 +1 @@ +uid://b1fvugn2vjief 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/TutorialTooltip.gd.uid b/Scripts/TutorialTooltip.gd.uid new file mode 100644 index 0000000..5e1ece1 --- /dev/null +++ b/Scripts/TutorialTooltip.gd.uid @@ -0,0 +1 @@ +uid://ccckld3okgh66 diff --git a/Scripts/both_blockage.gd.uid b/Scripts/both_blockage.gd.uid new file mode 100644 index 0000000..8b301e3 --- /dev/null +++ b/Scripts/both_blockage.gd.uid @@ -0,0 +1 @@ +uid://odnhfeutmjdi diff --git a/Scripts/broker.gd.uid b/Scripts/broker.gd.uid new file mode 100644 index 0000000..ae61bda --- /dev/null +++ b/Scripts/broker.gd.uid @@ -0,0 +1 @@ +uid://cam24iwnaw7yy diff --git a/Scripts/conveyor.gd.uid b/Scripts/conveyor.gd.uid new file mode 100644 index 0000000..9162fdd --- /dev/null +++ b/Scripts/conveyor.gd.uid @@ -0,0 +1 @@ +uid://bcun1ayw6n28i diff --git a/Scripts/dlqPattern.gd.uid b/Scripts/dlqPattern.gd.uid new file mode 100644 index 0000000..56e45da --- /dev/null +++ b/Scripts/dlqPattern.gd.uid @@ -0,0 +1 @@ +uid://cida2g53o8r2v diff --git a/Scripts/draggable_filter.gd.uid b/Scripts/draggable_filter.gd.uid new file mode 100644 index 0000000..760b889 --- /dev/null +++ b/Scripts/draggable_filter.gd.uid @@ -0,0 +1 @@ +uid://bygsdpswtckxt diff --git a/Scripts/event_box.gd b/Scripts/event_box.gd index b83bd02..61bec3a 100644 --- a/Scripts/event_box.gd +++ b/Scripts/event_box.gd @@ -37,8 +37,8 @@ func _ready() -> void: func _input_event(viewport, event, shape_idx) -> void: - print(event) - if event.is_pressed(): + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: + get_viewport().set_input_as_handled() self.on_click() func on_click(): diff --git a/Scripts/event_box.gd.uid b/Scripts/event_box.gd.uid new file mode 100644 index 0000000..648e53e --- /dev/null +++ b/Scripts/event_box.gd.uid @@ -0,0 +1 @@ +uid://dytl3q2sat4ew diff --git a/Scripts/event_box_clickable.gd.uid b/Scripts/event_box_clickable.gd.uid new file mode 100644 index 0000000..f7dd37e --- /dev/null +++ b/Scripts/event_box_clickable.gd.uid @@ -0,0 +1 @@ +uid://5ydkp5vyru6l diff --git a/Scripts/event_button.gd.uid b/Scripts/event_button.gd.uid new file mode 100644 index 0000000..cf51b60 --- /dev/null +++ b/Scripts/event_button.gd.uid @@ -0,0 +1 @@ +uid://0jf80xpverit diff --git a/Scripts/level.gd b/Scripts/level.gd index 5565b4c..d1512dc 100644 --- a/Scripts/level.gd +++ b/Scripts/level.gd @@ -8,6 +8,8 @@ var totalbox=[2,2,3] var nextLevel var levels=["basicEventFlow","boxClick","multiSink","dlqPattern"] var levelind=0 +var tutorial_lines = [] +var step_labels = [] func initialise(): sinkBoxMatchPresent=true @@ -15,6 +17,126 @@ func initialise(): totalbox=0 nextLevel=false dlsUsed=false + clear_tutorial_lines() + clear_step_labels() + +func _ready(): + _create_help_button() + get_tree().node_added.connect(_on_node_added) + # Check if already loaded (for first run) + var current = get_tree().current_scene + if current and current.name == "basicEventFlow": + _spawn_tutorial_lines(current) + _create_tutorial_steps(current) + +func _process(_delta): + if levelind == 0 and step_labels.size() >= 2: + var lbl1 = step_labels[0] + var lbl2 = step_labels[1] + if is_instance_valid(lbl1) and is_instance_valid(lbl2): + if ConveyerController.selected == null: + lbl1.visible = true + lbl2.visible = false + else: + lbl1.visible = false + lbl2.visible = true + +func _on_node_added(node): + if node.name == "basicEventFlow": + # Wait a frame for children to be ready + await get_tree().process_frame + _spawn_tutorial_lines(node) + _create_tutorial_steps(node) + +func _spawn_tutorial_lines(scene_root): + print("Spawning tutorial lines for Level 1...") + # Load the connector line scene + var line_scene = load("res://Scenes/ConnectorLine.tscn") + if not line_scene: return + + # Find nodes (hardcoded names based on basicEventFlow.tscn) + var box_a = scene_root.get_node_or_null("BoxA") + var box_b = scene_root.get_node_or_null("BoxB") + var sink = scene_root.get_node_or_null("Sink") + + # Connect BoxA -> Sink + if box_a and sink: + var line1 = line_scene.instantiate() + scene_root.add_child(line1) + line1.setup(box_a, sink) + tutorial_lines.append(line1) + + # Connect BoxB -> Sink + if box_b and sink: + var line2 = line_scene.instantiate() + scene_root.add_child(line2) + line2.setup(box_b, sink) + tutorial_lines.append(line2) + +func clear_tutorial_lines(): + for line in tutorial_lines: + if is_instance_valid(line): + line.fade_out() + tutorial_lines.clear() + +func _create_tutorial_steps(scene_root): + var box_a = scene_root.get_node_or_null("BoxA") + var sink = scene_root.get_node_or_null("Sink") + + if not box_a or not sink: return + + var label1 = Label.new() + label1.text = "1. Click a Box to Select" + # Move further left to avoid overlap with the box + label1.position = box_a.global_position + Vector2(-400, -30) + scene_root.add_child(label1) + step_labels.append(label1) + + var label2 = Label.new() + label2.text = "2. Now click the Sink" + # Move much further left to clear the sink graphic (was -350, now -550) + label2.position = sink.global_position + Vector2(-550, 0) + scene_root.add_child(label2) + step_labels.append(label2) + + # Style labels + for lbl in step_labels: + lbl.add_theme_font_size_override("font_size", 28) + lbl.add_theme_color_override("font_color", Color(1, 0.2, 0.2)) # High contrast red + +func clear_step_labels(): + for lbl in step_labels: + if is_instance_valid(lbl): + var tween = create_tween() + tween.tween_property(lbl, "modulate:a", 0.0, 0.3) + tween.finished.connect(lbl.queue_free) + step_labels.clear() + +func _create_help_button(): + # Create a CanvasLayer so button stays on top of everything + var canvas = CanvasLayer.new() + add_child(canvas) + + var help_btn = Button.new() + help_btn.text = "?" + help_btn.name = "GlobalHelpButton" + + # Style the button to look like a circle/help icon + help_btn.custom_minimum_size = Vector2(40, 40) + help_btn.position = Vector2(get_viewport().get_visible_rect().size.x - 60, 20) # Top right + + # Connect signal + help_btn.pressed.connect(_on_help_pressed) + + canvas.add_child(help_btn) + +func _on_help_pressed(): + print("Help button pressed!") + # Try access via singleton name directly first (if autoloaded) + if has_node("/root/TutorialManager"): + get_node("/root/TutorialManager").open_glossary() + else: + print("CRITICAL ERROR: TutorialManager not found in /root. Please add 'Scripts/TutorialManager.gd' to Project Settings -> Autoload as 'TutorialManager'") func next_level(): if sinkUsed: @@ -47,8 +169,44 @@ func next_level(): print("End of Levels.") get_tree().change_scene_to_file("res://Scenes/end_of_all_levels.tscn") else: + var failure_data = analyze_failure() + show_educational_failure(failure_data, message_display) + await message_display.show_message_for_duration(4.0) print("Failed. Try Again") AudioManager.play_level_fail() message_display.show_message("Failed. Try Again") await message_display.show_message_for_duration(2.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/level.gd.uid b/Scripts/level.gd.uid new file mode 100644 index 0000000..06905c1 --- /dev/null +++ b/Scripts/level.gd.uid @@ -0,0 +1 @@ +uid://bw6bxdjmdb7h3 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/Scripts/message_display.gd.uid b/Scripts/message_display.gd.uid new file mode 100644 index 0000000..f331284 --- /dev/null +++ b/Scripts/message_display.gd.uid @@ -0,0 +1 @@ +uid://chxe8vkdsleow diff --git a/Scripts/multi_sink.gd.uid b/Scripts/multi_sink.gd.uid new file mode 100644 index 0000000..1f1c4a3 --- /dev/null +++ b/Scripts/multi_sink.gd.uid @@ -0,0 +1 @@ +uid://n4c8672uung diff --git a/Scripts/multi_sink_start.gd.uid b/Scripts/multi_sink_start.gd.uid new file mode 100644 index 0000000..f53a75e --- /dev/null +++ b/Scripts/multi_sink_start.gd.uid @@ -0,0 +1 @@ +uid://dud6dvq05qg45 diff --git a/Scripts/restart.gd.uid b/Scripts/restart.gd.uid new file mode 100644 index 0000000..36894f6 --- /dev/null +++ b/Scripts/restart.gd.uid @@ -0,0 +1 @@ +uid://b4hbl1q7gcjns diff --git a/Scripts/scrolling_line.gdshader b/Scripts/scrolling_line.gdshader new file mode 100644 index 0000000..3fbc27f --- /dev/null +++ b/Scripts/scrolling_line.gdshader @@ -0,0 +1,10 @@ +shader_type canvas_item; + +uniform float speed = 2.0; + +void fragment() { + vec2 uv = UV; + uv.x -= TIME * speed; + vec4 color = texture(TEXTURE, uv); + COLOR = color * MODULATE; +} diff --git a/Scripts/scrolling_line.gdshader.uid b/Scripts/scrolling_line.gdshader.uid new file mode 100644 index 0000000..ec6005c --- /dev/null +++ b/Scripts/scrolling_line.gdshader.uid @@ -0,0 +1 @@ +uid://banpga7uk8v7q diff --git a/Scripts/sink.gd b/Scripts/sink.gd index 22c93bd..45289a2 100644 --- a/Scripts/sink.gd +++ b/Scripts/sink.gd @@ -1,15 +1,30 @@ 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: + 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 pass diff --git a/Scripts/sink.gd.uid b/Scripts/sink.gd.uid new file mode 100644 index 0000000..cf4ffa0 --- /dev/null +++ b/Scripts/sink.gd.uid @@ -0,0 +1 @@ +uid://dqgsvirkb1x2 diff --git a/project.godot b/project.godot index fdbdc1c..37eb537 100644 --- a/project.godot +++ b/project.godot @@ -8,17 +8,22 @@ config_version=5 +[animation] + +compatibility/default_parent_skeleton_in_mesh_instance_3d=true + [application] config/name="eventing-game" run/main_scene="res://Scenes/basicEventFlow.tscn" -config/features=PackedStringArray("4.3", "GL Compatibility") +config/features=PackedStringArray("4.6", "GL Compatibility") config/icon="res://icon.svg" [autoload] ConveyerController="*res://Scripts/ConveyerController.gd" Level="*res://Scripts/level.gd" +TutorialManager="*uid://b1fvugn2vjief" AudioManager="*res://Scripts/AudioManager.gd" [input] 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