From a478e3fc2ea487566a1967e56e6953c3b9ae60e2 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Sat, 11 Apr 2026 06:00:54 +0800 Subject: [PATCH] feat: completing tutorial --- export_presets.cfg | 2 +- scenes/lobby.tscn | 3 +- scenes/main.tscn | 8 +- scenes/player.tscn | 16 ++ scenes/tekton.tscn | 5 + scenes/tutorial/tutorial_overlay.tscn | 41 +++- scenes/ui/game_over_panel.tscn | 18 +- scenes/ui/login_screen.tscn | 2 +- scripts/managers/player_input_manager.gd | 4 +- scripts/managers/tutorial_manager.gd | 270 +++++++++++++++++------ scripts/managers/tutorial_overlay.gd | 132 ++++++----- 11 files changed, 360 insertions(+), 141 deletions(-) diff --git a/export_presets.cfg b/export_presets.cfg index 7cf6cc8..f37b963 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -8,7 +8,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/tekton_armageddon_v2.1.2.exe" +export_path="build/tekton_armageddon_v2.1.3.exe" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn index 1311797..7357e83 100644 --- a/scenes/lobby.tscn +++ b/scenes/lobby.tscn @@ -1156,6 +1156,7 @@ text = "NOT CONNECTED" horizontal_alignment = 1 [node name="VersionLabel" type="Label" parent="." unique_id=351711152] +visible = false layout_mode = 1 anchors_preset = 3 anchor_left = 1.0 @@ -1170,5 +1171,5 @@ grow_horizontal = 0 grow_vertical = 0 theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 0.6) theme_override_font_sizes/font_size = 11 -text = "v0.1.0 ALPHA" +text = "v.2.1.3" horizontal_alignment = 2 diff --git a/scenes/main.tscn b/scenes/main.tscn index 6723a2e..3f1caca 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1727,7 +1727,7 @@ expand_icon = true [node name="PauseMenu" type="CanvasLayer" parent="." unique_id=181131829] process_mode = 3 -layer = 10 +layer = 1002 visible = false [node name="Background" type="ColorRect" parent="PauseMenu" unique_id=412985431] @@ -2007,9 +2007,11 @@ layout_mode = 2 text = "Back" [node name="TopMenuUI" type="CanvasLayer" parent="." unique_id=1234567890] -layer = 2 +layer = 102 [node name="HelpBtn" type="Button" parent="TopMenuUI" unique_id=833886895] +top_level = true +z_index = 2000 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 @@ -2024,6 +2026,8 @@ icon = ExtResource("37_pibwh") flat = true [node name="SettingsBtn" type="Button" parent="TopMenuUI" unique_id=1964422444] +top_level = true +z_index = 2000 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 diff --git a/scenes/player.tscn b/scenes/player.tscn index 70c21eb..ee818ed 100644 --- a/scenes/player.tscn +++ b/scenes/player.tscn @@ -68,6 +68,9 @@ shape = SubResource("SphereShape3D_3oo5r") [node name="Name" type="Label3D" parent="." unique_id=848046946] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0281162, 0) billboard = 1 +no_depth_test = true +render_priority = 2 +outline_render_priority = 1 text = "username" font = ExtResource("8_y4r1p") font_size = 48 @@ -78,6 +81,9 @@ autowrap_mode = 2 [node name="Position" type="Label3D" parent="." unique_id=482425681] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.537, 0) billboard = 1 +no_depth_test = true +render_priority = 2 +outline_render_priority = 1 modulate = Color(0.32, 0.614667, 1, 1) text = "1st" font = ExtResource("8_y4r1p") @@ -114,6 +120,7 @@ script = ExtResource("7_botctrl") transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 1.5653763, 0) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"freeze-initiator" frame = 149 @@ -123,6 +130,7 @@ frame_progress = 1.0 transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 1.5653763, 0) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"ghost-initiator" frame = 149 @@ -132,6 +140,7 @@ frame_progress = 1.0 transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 1.5653763, 0) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"speed-initator" frame = 149 @@ -141,6 +150,7 @@ frame_progress = 1.0 transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 1.5653763, 0) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"wall-initiator" @@ -148,6 +158,7 @@ animation = &"wall-initiator" transform = Transform3D(0.5, 0, 0, 0, 0.5, 1.509958e-07, 0, -1.509958e-07, 0.5, 0, 1.5475016, 0.012766336) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"scatter_knock" frame = 59 @@ -157,6 +168,7 @@ frame_progress = 1.0 transform = Transform3D(0.5, 0, 0, 0, -0.5, -7.54979e-08, 0, 7.54979e-08, -0.5, 0, 1.4714267, 0.02553294) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"floor_spawn_top" frame = 23 @@ -165,6 +177,7 @@ frame_progress = 1.0 [node name="floor_spawn_bot" type="AnimatedSprite3D" parent="." unique_id=169253263] transform = Transform3D(1.2, 0, 0, 0, -5.2453668e-08, -1.2, 0, 1.2, -5.2453668e-08, 0, -0.506722, -4.4299043e-08) visible = false +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"floor_spawn_bot" frame = 35 @@ -174,6 +187,7 @@ frame_progress = 1.0 transform = Transform3D(0.5, 0, 0, 0, -2.1855694e-08, -0.5, 0, 0.5, -2.1855694e-08, 0, 1.5653763, 0) visible = false billboard = 1 +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"stunned-receiver" frame = 59 @@ -182,12 +196,14 @@ frame_progress = 1.0 [node name="attack_mode_top" type="AnimatedSprite3D" parent="." unique_id=2002706555] transform = Transform3D(0.5, 0, 0, 0, -2.1855694e-08, -0.5, 0, 0.5, -2.1855694e-08, 0, 1.5653763, 0) visible = false +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"attack-mode-receiver-top" [node name="attack_mode_bot" type="AnimatedSprite3D" parent="." unique_id=1320274503] transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, -0.6701627, 0) visible = false +render_priority = 3 sprite_frames = ExtResource("10_y4r1p") animation = &"attack-mode-receiver-bot" frame = 31 diff --git a/scenes/tekton.tscn b/scenes/tekton.tscn index b701dbe..eebdc7b 100644 --- a/scenes/tekton.tscn +++ b/scenes/tekton.tscn @@ -34,6 +34,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3731489, 0) pixel_size = 0.002 billboard = 1 no_depth_test = true +render_priority = 1 texture = ExtResource("4_grab_icon") [node name="KeyLabel" type="Label3D" parent="InteractionPrompt" unique_id=1816493043] @@ -41,6 +42,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.30466843, 0) pixel_size = 0.01 billboard = 1 no_depth_test = true +render_priority = 2 +outline_render_priority = 1 modulate = Color(0.1, 1, 0.2, 1) text = "[ G ]" font_size = 48 @@ -51,6 +54,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.79176676, 0) pixel_size = 0.01 billboard = 1 no_depth_test = true +render_priority = 2 +outline_render_priority = 1 text = "LIFT TEKTON" font_size = 38 outline_size = 10 diff --git a/scenes/tutorial/tutorial_overlay.tscn b/scenes/tutorial/tutorial_overlay.tscn index 9492c88..5eb06f5 100644 --- a/scenes/tutorial/tutorial_overlay.tscn +++ b/scenes/tutorial/tutorial_overlay.tscn @@ -54,7 +54,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -mouse_filter = 2 +mouse_filter = 1 color = Color(0, 0, 0, 1) [node name="HighlightBorder" type="Panel" parent="." unique_id=25061739] @@ -157,3 +157,42 @@ offset_left = 1080.0 offset_top = 96.0 offset_right = 1356.0 offset_bottom = 405.0 + +[node name="ObjectivePanel" type="PanelContainer" parent="." unique_id=-1098906247] +visible = false +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -315.0 +offset_top = -210.0 +offset_right = -15.0 +offset_bottom = -130.0 +grow_horizontal = 0 +grow_vertical = 0 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_panel") + +[node name="MarginContainer" type="MarginContainer" parent="ObjectivePanel" unique_id=-197916117] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="ObjectivePanel/MarginContainer" unique_id=1029384756] +layout_mode = 2 + +[node name="Title" type="Label" parent="ObjectivePanel/MarginContainer/VBoxContainer" unique_id=1352415614] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.85, 0.1, 1) +theme_override_font_sizes/font_size = 14 +text = "CURRENT OBJECTIVE" + +[node name="ObjectiveText" type="RichTextLabel" parent="ObjectivePanel/MarginContainer/VBoxContainer" unique_id=895826431] +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 18 +bbcode_enabled = true +text = "Objective text goes here" +fit_content = true diff --git a/scenes/ui/game_over_panel.tscn b/scenes/ui/game_over_panel.tscn index b765caa..be109a6 100644 --- a/scenes/ui/game_over_panel.tscn +++ b/scenes/ui/game_over_panel.tscn @@ -573,16 +573,16 @@ theme_override_styles/pressed = SubResource("StyleBoxTexture_3r3oo") theme_override_styles/hover = SubResource("StyleBoxTexture_cn37d") [node name="AchievementPanel" type="Panel" parent="MainMargin/MainHBox/BottomWrapper" unique_id=689276483] -custom_minimum_size = Vector2(165.5, 208) +custom_minimum_size = Vector2(82.75, 104) layout_mode = 1 anchors_preset = -1 -anchor_left = 0.35300002 -anchor_top = -1.9760001 -anchor_right = 0.45800003 -anchor_bottom = 0.42600003 -offset_left = -0.08200073 -offset_top = 0.008010864 -offset_right = 19.047974 -offset_bottom = 8.641998 +anchor_left = 0.377 +anchor_top = -1.1520001 +anchor_right = 0.44700003 +anchor_bottom = 0.407 +offset_left = 0.4619751 +offset_top = -11.383995 +offset_right = 13.421753 +offset_bottom = -1.8549156 rotation = 0.10297442 theme_override_styles/panel = SubResource("StyleBoxTexture_abdxb") diff --git a/scenes/ui/login_screen.tscn b/scenes/ui/login_screen.tscn index e430c85..c067f36 100644 --- a/scenes/ui/login_screen.tscn +++ b/scenes/ui/login_screen.tscn @@ -328,5 +328,5 @@ grow_horizontal = 0 grow_vertical = 0 theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 0.6) theme_override_font_sizes/font_size = 11 -text = "v2.1.0" +text = "v2.1.2" horizontal_alignment = 2 diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index e65973a..96d5bee 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -55,7 +55,9 @@ func _process(delta): base_pos = player.target_position var target_position = base_pos + move_vec movement_manager.simple_move_to(target_position) - + else: + if movement_manager.is_moving: + movement_manager.movement_queue.clear() func handle_unhandled_input(event): diff --git a/scripts/managers/tutorial_manager.gd b/scripts/managers/tutorial_manager.gd index 97998bf..d9179be 100644 --- a/scripts/managers/tutorial_manager.gd +++ b/scripts/managers/tutorial_manager.gd @@ -24,6 +24,10 @@ func _ready() -> void: break await get_tree().process_frame elapsed += get_process_delta_time() + + # Pause match timer during tutorial + if main_scene and main_scene.get("goals_cycle_manager"): + main_scene.goals_cycle_manager.set_process(false) if not local_player: print("Tutorial Error: Local player not found.") @@ -78,15 +82,17 @@ func _run_tutorial() -> void: # Grid Highlight overlay.clear_highlight() await overlay.display_text("Look at the board! You will see [color=gold]Normal Tiles[/color] and shiny [color=cyan]Holo Tiles[/color].", true) - + # ============================================================================== # --- INTERACTIVE 1: NORMAL TILE --- - var normal_pos = _find_closest_tile(local_player.current_position, [7, 8, 9, 10]) + # ============================================================================== + var normal_pos = _find_closest_tile(local_player.current_position, [7, 8, 9, 10], 4) if normal_pos != Vector2i(-1, -1): _highlight_floor_cell(normal_pos, Color(1.0, 1.0, 0.0)) # Yellow box overlay.display_text("I've highlighted a [color=gold]Normal Tile[/color] with a yellow box. Walk over to it and press [color=red]SPACE[/color] to Grab it!", false) else: overlay.display_text("Walk up to any Normal Tile and press [color=red]SPACE[/color] to Grab it!", false) + overlay.set_objective("Walk to a [color=gold]Normal Tile[/color] and press [color=red]SPACE[/color] to grab it.") overlay.hide_overlay() get_tree().paused = false @@ -100,13 +106,15 @@ func _run_tutorial() -> void: break await get_tree().process_frame + overlay.hide_objective() _clear_floor_highlights() get_tree().paused = true overlay.show_overlay() await overlay.display_text("Outstanding! You secured a tile.", true) - + # ============================================================================== # --- SCORE AND GOALS SECTION --- + # ============================================================================== overlay.highlight_zone("Leaderboard") await overlay.display_text("Your main objective is to climb the [color=gold]Leaderboard[/color] on the right by scoring points.", true) @@ -114,54 +122,159 @@ func _run_tutorial() -> void: await overlay.display_text("You earn points by collecting tiles, but the real score comes from completing your [color=gold]Playerboard Goals[/color]!", true) await overlay.display_text("Fill your entire board shape to complete a goal. This grants massive points, clears the board, and ranks you up!", true) - # --- TEKTONS SECTION --- - overlay.clear_highlight() - await overlay.display_text("Now, what about those little creatures running around the map? Those are [color=cyan]Tektons[/color]!", true) - await overlay.display_text("Grabbing a dynamic Tekton gives you [color=gold]PASSIVE score points[/color] over time and spawns bonus tiles for you while you carry it.", true) - await overlay.display_text("Hold onto them as long as you can... but watch out, opponents will try to RAM you and steal them!", true) - await overlay.display_text("Also keep an eye on [color=gray]Static Tektons[/color] standing on podiums. They periodically throw new free tiles onto the board for you to collect!", true) - - # --- INTERACTIVE 2: HOLO TILE --- + # ============================================================================== + # --- POWER UP SECTION --- + # ============================================================================== await overlay.display_text("Now, let's talk about [color=cyan]Holo Tiles[/color]. They grant you special Power-Ups!", true) - var holo_pos = _find_closest_tile(local_player.current_position, [11, 12, 13, 14]) - if holo_pos != Vector2i(-1, -1): - _highlight_floor_cell(holo_pos, Color(0.0, 1.0, 1.0)) # Cyan box - overlay.display_text("I've highlighted a [color=cyan]Holo Tile[/color] with a cyan box. Go ahead and grab it!", false) - else: - # Fallback: force spawn one if the RNG map generation didn't place any - holo_pos = local_player.current_position + Vector2i(0, 1) - var gridmap = local_player.get("enhanced_gridmap") - if gridmap: - gridmap.set_cell_item(Vector3i(holo_pos.x, 1, holo_pos.y), 11) # Spawn speed boost - _highlight_floor_cell(holo_pos, Color(0.0, 1.0, 1.0)) - overlay.display_text("I just dropped a Holo Tile near you (cyan box). Go ahead and grab it!", false) + overlay.clear_highlight() + await overlay.display_text("There are [color=cyan]4 types of Power-Ups[/color] from Holo Tiles. Let me walk you through each one, one by one.", true) + var powerup_ids = [11, 12, 13, 14] + var powerup_names = { + 11: "[color=gold]Speed Boost[/color] (Make you run fast)", + 12: "[color=aqua]Area Freeze[/color] (Slows nearby opponents)", + 13: "[color=gray]Iron Wall[/color] (Blocks opponent path)", + 14: "[color=white]Ghost Mode[/color] (Invisibility and ram immunity)" + } + var powerup_enums = {11: 0, 12: 1, 13: 2, 14: 3} + var gridmap = local_player.get("enhanced_gridmap") + + for pid in powerup_ids: + overlay.clear_highlight() + overlay.show_powerup_card(pid) + await overlay.display_text("Next is %s. I will drop this Holo Tile near you." % powerup_names[pid], true) + overlay.hide_powerup_card() + + var ppos = local_player.current_position + Vector2i(0, 1) + if gridmap: + gridmap.set_cell_item(Vector3i(ppos.x, 1, ppos.y), pid) + _highlight_floor_cell(ppos, Color(0.0, 1.0, 1.0)) + + overlay.display_text("Grab the tile, press [color=yellow]'F'[/color] to use it, and for some skills press it again or Click to confirm!", false) + + overlay.set_objective("Grab the [color=cyan]Holo Tile[/color] and press [color=yellow]'F'[/color] to use %s." % powerup_names[pid]) + overlay.hide_overlay() + get_tree().paused = false + + # Wait for it to be grabbed + while gridmap and gridmap.get_cell_item(Vector3i(ppos.x, 1, ppos.y)) == pid: + await get_tree().process_frame + + # Highlight the Power-Up item slot now that they picked it up + overlay.highlight_zone("PowerUpItem") + + # Wait for it to be used + var effect_enum = powerup_enums[pid] + while local_player.get("special_tiles_manager") and local_player.special_tiles_manager.inventory.get(effect_enum, false) == true: + await get_tree().process_frame + + overlay.clear_highlight() + + overlay.hide_objective() + _clear_floor_highlights() + get_tree().paused = true + overlay.show_overlay() + await overlay.display_text("Excellent! You used %s." % powerup_names[pid], true) + + # ============================================================================== + # --- TEKTONS SECTION --- + # ============================================================================== + overlay.clear_highlight() + # --- DYNAMIC TEKTON FOCUS --- + var dynamic_tektons = get_tree().get_nodes_in_group("Tektons") + var cam_mgr = main_scene.get("camera_context_manager") if main_scene else null + if dynamic_tektons.size() > 0 and cam_mgr: + # Find nearest dynamic tekton + var nearest = null + var nearest_dist = 999999 + for tk in dynamic_tektons: + if tk.get("is_static_turret") == false: + var dist = local_player.global_position.distance_to(tk.global_position) + if dist < nearest_dist: + nearest_dist = dist + nearest = tk + + if nearest: + cam_mgr.process_mode = Node.PROCESS_MODE_ALWAYS + cam_mgr.set_player(nearest) + await get_tree().create_timer(1.2, true).timeout + + var cam = main_scene.get_node_or_null("Camera3D200") + if cam and cam is Camera3D: + var screen_pos = cam.unproject_position(nearest.global_position) + overlay.highlight_rect(Rect2(screen_pos.x - 75, screen_pos.y - 75, 150, 150)) + + await overlay.display_text("Now, what about those little creatures running around the map? Those are [color=cyan]Tektons[/color]!", true) + + if cam_mgr: + cam_mgr.set_player(local_player) + cam_mgr.process_mode = Node.PROCESS_MODE_INHERIT + overlay.clear_highlight() + await get_tree().create_timer(0.8, true).timeout + # ---------------------------- + + await overlay.display_text("Grabbing a dynamic Tekton gives you [color=gold]PASSIVE score points[/color] over time and spawns bonus tiles for you while you carry it.", true) + await overlay.display_text("Hold onto them as long as you can... but watch out, opponents will try to RAM you and steal them!", true) + + await overlay.display_text("Go and catch one of the Tektons running around!", false) + overlay.set_objective("Walk into a [color=cyan]Tekton[/color] or press [color=red]G[/color] near one to grab it.") overlay.hide_overlay() get_tree().paused = false - initial_items = local_player.playerboard.count(-1) - while true: - if holo_pos != Vector2i(-1, -1) and local_player.get("enhanced_gridmap"): - if local_player.enhanced_gridmap.get_cell_item(Vector3i(holo_pos.x, 1, holo_pos.y)) == -1: - break - elif local_player.playerboard.count(-1) < initial_items: - break + while not local_player.is_carrying_tekton: await get_tree().process_frame - - _clear_floor_highlights() + + overlay.hide_objective() get_tree().paused = true overlay.show_overlay() + await overlay.display_text("Great job! Notice your score passively increasing.", true) - # --- POWER UP SECTION --- - # Highlight Power Up Icon - overlay.highlight_zone("PowerUpItem") - await overlay.display_text("Nice! You grabbed a Holo Tile — that earned you a special [color=cyan]Power-Up[/color] item!", true) - await overlay.display_text("There are [color=cyan]4 types of Power-Ups[/color] from Holo Tiles. Let me walk you through each one.", true) - await overlay.show_powerup_showcase() - await overlay.display_text("To activate your stored Power-Up, press [color=yellow]'F'[/color]. If you don't like this key, you can remap it in [color=gold]Settings[/color].", true) + await overlay.display_text("But you won't hold it forever! The Tekton runs away when your carry timer reaches 0, or you can manually throw it by pressing [color=red]G[/color].", false) + overlay.set_objective("Wait for the [color=cyan]Tekton[/color] to escape, or press [color=red]G[/color] to throw it!") + overlay.hide_overlay() + get_tree().paused = false + while local_player.is_carrying_tekton: + await get_tree().process_frame + + overlay.hide_objective() + get_tree().paused = true + overlay.show_overlay() + await overlay.display_text("Oops, it got away! But don't worry, you can always catch another one.", true) + + # ============================================================================== + # --- STATIC TEKTON FOCUS --- + # ============================================================================== + var static_stands = get_tree().get_nodes_in_group("StaticTektonStands") + cam_mgr = main_scene.get("camera_context_manager") if main_scene else null + + if static_stands.size() > 0 and cam_mgr: + var target_stand = static_stands[0] + cam_mgr.process_mode = Node.PROCESS_MODE_ALWAYS # Allow camera to slide while paused + cam_mgr.set_player(target_stand) + + # Give it time to pan over + await get_tree().create_timer(1.2, true).timeout + + # Dim the screen around it + var cam = main_scene.get_node_or_null("Camera3D200") + if cam and cam is Camera3D: + var screen_pos = cam.unproject_position(target_stand.global_position) + overlay.highlight_rect(Rect2(screen_pos.x - 100, screen_pos.y - 120, 200, 200)) + + await overlay.display_text("Also keep an eye on [color=gray]Static Tektons[/color] standing on podiums. They periodically throw new free tiles onto the board for you to collect!", true) + + if cam_mgr: + cam_mgr.set_player(local_player) + cam_mgr.process_mode = Node.PROCESS_MODE_INHERIT + overlay.clear_highlight() + + # Let it pan back briefly + await get_tree().create_timer(0.8, true).timeout + # ============================================================================== # --- BATTERY GAUGE & RAMMING SECTION --- + # ============================================================================== # Switch to highlight Power Bar / Battery Gauge overlay.highlight_zone("PowerBar") await overlay.display_text("Next, look at your [color=cyan]Battery Gauge[/color] — it [color=yellow]fills up automatically over time[/color]. You don't need to do anything to charge it.", true) @@ -187,15 +300,41 @@ func _run_tutorial() -> void: spawned_bot.add_to_group("Bots", true) await get_tree().create_timer(0.2).timeout - # Freeze it so its AI doesn't run away during the tutorial var bc = spawned_bot.get_node_or_null("BotController") if bc: - bc.set_process(false) - bc.set_physics_process(false) - spawned_bot.set("is_frozen", true) + bc.set_process(true) + bc.set_physics_process(true) if spawned_bot: - await overlay.display_text("Let's try it. I'm placing a Bot right next to you, and I'll instantly fill your Battery Gauge to max.", true) + # Always unfreeze in case it was caught in the earlier Area Freeze blast + spawned_bot.set("is_frozen", false) + var target_bc = spawned_bot.get_node_or_null("BotController") + if target_bc: + target_bc.set_process(true) + target_bc.set_physics_process(true) + + await overlay.display_text("Let's try it. There is a Bot roaming the map right now. I will fill your Battery Gauge to max.", true) + + # --- CAMERA PAN TO BOT --- + cam_mgr = main_scene.get("camera_context_manager") if main_scene else null + if cam_mgr: + cam_mgr.process_mode = Node.PROCESS_MODE_ALWAYS + cam_mgr.set_player(spawned_bot) + await get_tree().create_timer(1.2, true).timeout + + var cam = main_scene.get_node_or_null("Camera3D200") + if cam and cam is Camera3D: + var screen_pos = cam.unproject_position(spawned_bot.global_position) + overlay.highlight_rect(Rect2(screen_pos.x - 75, screen_pos.y - 100, 150, 150)) + + await overlay.display_text("Behold, your opponent! Their AI is active so you will have to hunt them down.", true) + + if cam_mgr: + cam_mgr.set_player(local_player) + cam_mgr.process_mode = Node.PROCESS_MODE_INHERIT + overlay.clear_highlight() + await get_tree().create_timer(0.8, true).timeout + # ------------------------ # Instantly grant FULL BOOST if local_player.get("powerup_manager"): @@ -204,41 +343,42 @@ func _run_tutorial() -> void: local_player.powerup_manager.acquire_smash_bonus() local_player.powerup_manager.acquire_smash_bonus() - var n_pos = local_player.current_position + Vector2i(1, 0) - if local_player.get("enhanced_gridmap"): - var neighbors = local_player.enhanced_gridmap.get_neighbors(local_player.current_position, 0) - for n in neighbors: - if n.get("is_walkable"): - n_pos = n.position - break - - # Safely set bot position - spawned_bot.current_position = n_pos - if local_player.get("enhanced_gridmap") and local_player.enhanced_gridmap.has_method("grid_to_world"): - spawned_bot.global_position = local_player.enhanced_gridmap.grid_to_world(n_pos) - - overlay.display_text("Press 'Q' to enter Attack Mode, then Walk into the Bot!", false) + overlay.display_text("Press 'Q' to enter Attack Mode, then chase the Bot down to RAM it!", false) + overlay.set_objective("Press [color=yellow]'Q'[/color] to enter [color=red]ATTACK MODE[/color], then chase and RAM the bot!") overlay.hide_overlay() get_tree().paused = false - # Wait for the player to push the bot (bot's position changes) - var bot_old_pos = spawned_bot.current_position - while spawned_bot.current_position == bot_old_pos: + # Wait for the player to push the bot + while true: + if spawned_bot.get("is_knock_mode") == true: + break + + # Fallback: if player's attack mode deactivated early despite having timer left, they likely successfully rammed + var t = local_player.get("attack_mode_timer") + if local_player.get("is_attack_mode") == false and t != null and t > 0.0 and t < 3.0: + break + await get_tree().process_frame + overlay.hide_objective() get_tree().paused = true overlay.show_overlay() await overlay.display_text("Smashing! You pushed it away. Notice your Battery Gauge was consumed.", true) await overlay.display_text("Beware! Your opponents can RAM you to steal your tiles too. Get ready to defend yourself against the Bots!", true) + await overlay.display_text("Tutorial Complete! Returning to the Main Menu.", true) + overlay.hide_overlay() get_tree().paused = false - _set_bots_enabled(true) + if main_scene and main_scene.get("goals_cycle_manager"): + main_scene.goals_cycle_manager.set_process(true) + LobbyManager.is_tutorial_mode = false - queue_free() + LobbyManager.leave_room() + get_tree().change_scene_to_file("res://scenes/lobby.tscn") # ============================================================================== # Helper Floor Highlighting and Tile Detection Logic @@ -246,7 +386,7 @@ func _run_tutorial() -> void: var _floor_highlights: Array[Node] = [] -func _find_closest_tile(start_pos: Vector2i, types: Array) -> Vector2i: +func _find_closest_tile(start_pos: Vector2i, types: Array, min_dist: int = 0) -> Vector2i: var gridmap = local_player.get("enhanced_gridmap") if not gridmap: return Vector2i(-1, -1) @@ -258,7 +398,7 @@ func _find_closest_tile(start_pos: Vector2i, types: Array) -> Vector2i: var item = gridmap.get_cell_item(Vector3i(x, 1, z)) if item in types: var dist = abs(start_pos.x - x) + abs(start_pos.y - z) - if dist < best_dist: + if dist >= min_dist and dist < best_dist: best_dist = dist best_pos = Vector2i(x, z) return best_pos diff --git a/scripts/managers/tutorial_overlay.gd b/scripts/managers/tutorial_overlay.gd index 8bba3f7..6d0bf70 100644 --- a/scripts/managers/tutorial_overlay.gd +++ b/scripts/managers/tutorial_overlay.gd @@ -2,12 +2,15 @@ extends CanvasLayer class_name TutorialOverlay # --- Scene node references --- -@onready var dim_rect: ColorRect = $DimRect -@onready var highlight_border: Panel = $HighlightBorder +@onready var dim_rect: ColorRect = $DimRect +@onready var highlight_border: Panel = $HighlightBorder @onready var dialogue_panel: PanelContainer = $DialoguePanel -@onready var text_label: RichTextLabel = $DialoguePanel/MarginContainer/HBoxContainer/VBoxContainer/DialogueText -@onready var next_indicator: Label = $DialoguePanel/MarginContainer/HBoxContainer/VBoxContainer/NextIndicator -@onready var highlight_zones: Node = $HighlightZones +@onready var text_label: RichTextLabel = $DialoguePanel/MarginContainer/HBoxContainer/VBoxContainer/DialogueText +@onready var next_indicator: Label = $DialoguePanel/MarginContainer/HBoxContainer/VBoxContainer/NextIndicator +@onready var highlight_zones: Node = $HighlightZones + +@onready var objective_panel: PanelContainer = $ObjectivePanel +@onready var objective_text: RichTextLabel = $ObjectivePanel/MarginContainer/VBoxContainer/ObjectiveText # --- State --- var is_waiting_for_input: bool = false @@ -63,15 +66,15 @@ func highlight_rect(rect: Rect2) -> void: # Position the glowing border panel on top highlight_border.position = rect.position - Vector2(6, 6) - highlight_border.size = rect.size + Vector2(12, 12) - highlight_border.visible = true + highlight_border.size = rect.size + Vector2(12, 12) + highlight_border.visible = true # Pulsing glow animation if _pulse_tween: _pulse_tween.kill() _pulse_tween = create_tween().set_loops() _pulse_tween.tween_property(highlight_border, "self_modulate:a", 0.25, 0.75) - _pulse_tween.tween_property(highlight_border, "self_modulate:a", 1.0, 0.75) + _pulse_tween.tween_property(highlight_border, "self_modulate:a", 1.0, 0.75) func clear_highlight() -> void: if _dim_material: @@ -86,14 +89,23 @@ func clear_highlight() -> void: # Show / Hide whole overlay # --------------------------------------------------------------------------- func hide_overlay() -> void: - dim_rect.visible = false + dim_rect.visible = false highlight_border.visible = false - dialogue_panel.visible = false + dialogue_panel.visible = false func show_overlay() -> void: - dim_rect.visible = true + dim_rect.visible = true dialogue_panel.visible = true +func set_objective(text: String) -> void: + if objective_panel and objective_text: + objective_text.text = text + objective_panel.visible = true + +func hide_objective() -> void: + if objective_panel: + objective_panel.visible = false + # --------------------------------------------------------------------------- # Dialogue # --------------------------------------------------------------------------- @@ -106,11 +118,11 @@ func display_text(bbcode_text: String, wait_for_click: bool = true) -> void: if wait_for_click: next_indicator.visible = true - is_waiting_for_input = true + is_waiting_for_input = true await next_pressed else: next_indicator.visible = false - is_waiting_for_input = false + is_waiting_for_input = false func _input(event: InputEvent) -> void: if not is_waiting_for_input: @@ -121,38 +133,46 @@ func _input(event: InputEvent) -> void: elif event is InputEventScreenTouch and event.pressed: consumed = true if consumed: - is_waiting_for_input = false + is_waiting_for_input = false next_indicator.visible = false next_pressed.emit() - get_viewport().set_input_as_handled() + var vp = get_viewport() + if vp: + vp.set_input_as_handled() # --------------------------------------------------------------------------- # Power-Up Showcase — one card per powerup with icon + name + description # --------------------------------------------------------------------------- -func show_powerup_showcase() -> void: - """Display each powerup as an interactive card. Awaits click between each.""" - var powerups = [ - { +var _current_showcase: Control + +func show_powerup_card(pid: int) -> void: + var powerups = { + 11: { "icon": "res://assets/graphics/touch_control/speed.png", - "name": "[color=gold]⚡ Speed Boost[/color]", + "name": "[color=gold]Speed Boost[/color]", "desc": "Temporarily increases your movement speed for [color=yellow]5 seconds[/color]. Great for racing across the board to grab tiles before opponents." }, - { + 12: { "icon": "res://assets/graphics/touch_control/freeze_area.png", - "name": "[color=aqua]❄ Area Freeze[/color]", + "name": "[color=aqua]Area Freeze[/color]", "desc": "Freezes all opponents within a radius of [color=aqua]5 tiles[/color] around you, slowing them to a crawl for [color=yellow]3 seconds[/color]." }, - { + 13: { "icon": "res://assets/graphics/touch_control/wall.png", - "name": "[color=gray]🧱 Iron Wall[/color]", + "name": "[color=gray]Iron Wall[/color]", "desc": "Projects a full row or column of wall tiles in front of you, blocking opponent movement and protecting your tiles." }, - { + 14: { "icon": "res://assets/graphics/touch_control/ghost.png", - "name": "[color=white]👻 Ghost Mode[/color]", + "name": "[color=white]Ghost Mode[/color]", "desc": "Makes you [color=white]invisible[/color] for [color=yellow]6 seconds[/color]. You cannot be rammed while invisible — perfect for escaping danger or sneaking around." - }, - ] + } + } + + if not powerups.has(pid): + return + + var pu = powerups[pid] # Build a showcase panel sitting above the dialogue panel var showcase = PanelContainer.new() @@ -161,22 +181,22 @@ func show_powerup_showcase() -> void: var showcase_style = StyleBoxFlat.new() showcase_style.bg_color = Color(0.05, 0.05, 0.12, 0.97) - showcase_style.border_width_left = 3 - showcase_style.border_width_top = 3 - showcase_style.border_width_right = 3 + showcase_style.border_width_left = 3 + showcase_style.border_width_top = 3 + showcase_style.border_width_right = 3 showcase_style.border_width_bottom = 3 showcase_style.border_color = Color(0.6, 0.4, 1.0, 1.0) - showcase_style.corner_radius_top_left = 12 - showcase_style.corner_radius_top_right = 12 - showcase_style.corner_radius_bottom_left = 12 + showcase_style.corner_radius_top_left = 12 + showcase_style.corner_radius_top_right = 12 + showcase_style.corner_radius_bottom_left = 12 showcase_style.corner_radius_bottom_right = 12 showcase.add_theme_stylebox_override("panel", showcase_style) add_child(showcase) var margin = MarginContainer.new() - margin.add_theme_constant_override("margin_left", 24) - margin.add_theme_constant_override("margin_top", 16) - margin.add_theme_constant_override("margin_right", 24) + margin.add_theme_constant_override("margin_left", 24) + margin.add_theme_constant_override("margin_top", 16) + margin.add_theme_constant_override("margin_right", 24) margin.add_theme_constant_override("margin_bottom", 16) showcase.add_child(margin) @@ -187,9 +207,12 @@ func show_powerup_showcase() -> void: # Icon var icon_rect = TextureRect.new() icon_rect.custom_minimum_size = Vector2(96, 96) - icon_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon_rect.size_flags_vertical = Control.SIZE_SHRINK_CENTER + var tex = load(pu["icon"]) + if tex: + icon_rect.texture = tex hbox.add_child(icon_rect) # Text column @@ -203,6 +226,7 @@ func show_powerup_showcase() -> void: name_label.fit_content = true name_label.scroll_active = false name_label.add_theme_font_size_override("normal_font_size", 24) + name_label.text = pu["name"] vbox.add_child(name_label) var desc_label = RichTextLabel.new() @@ -211,36 +235,24 @@ func show_powerup_showcase() -> void: desc_label.scroll_active = false desc_label.size_flags_vertical = Control.SIZE_EXPAND_FILL desc_label.add_theme_font_size_override("normal_font_size", 18) + desc_label.text = pu["desc"] vbox.add_child(desc_label) - # Counter label (e.g. "1 / 4") - var counter_label = Label.new() - counter_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - counter_label.add_theme_font_size_override("font_size", 14) - counter_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.8)) - vbox.add_child(counter_label) - # Position the showcase above the dialogue panel + # We rely on call deferred or await frame to ensure layout sizing is processed await get_tree().process_frame + if not is_inside_tree() or not is_instance_valid(showcase): + return + var vp = get_viewport().get_visible_rect().size showcase.position = Vector2( (vp.x - showcase.custom_minimum_size.x) / 2.0, dialogue_panel.position.y - showcase.custom_minimum_size.y - 12 ) - # Cycle through each powerup - for i in powerups.size(): - var pu = powerups[i] - var tex = load(pu["icon"]) - if tex: - icon_rect.texture = tex - name_label.text = pu["name"] - desc_label.text = pu["desc"] - counter_label.text = "Power-Up %d / %d — Click to continue ▼" % [i + 1, powerups.size()] + _current_showcase = showcase - # Reuse the existing input wait mechanism - is_waiting_for_input = true - await next_pressed - - # Clean up showcase - showcase.queue_free() +func hide_powerup_card() -> void: + if is_instance_valid(_current_showcase): + _current_showcase.queue_free() + _current_showcase = null