From 1585b9150979ad8fe76e0d74d430b3a3ec2d2f93 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Tue, 28 Apr 2026 01:22:38 +0800 Subject: [PATCH] feat: update --- CHANGELOG_DRAFT.md | 10 +- assets/data/version.json | 15 +- scenes/main.gd | 352 ++++++++++++++++------ scenes/main.tscn | 7 + scenes/player.gd | 10 + scenes/tools/skin_shader_generator.tscn | 251 ++++++++------- scripts/managers/skin_manager.gd | 28 +- scripts/managers/static_tekton_manager.gd | 62 ++-- scripts/tekton.gd | 23 +- 9 files changed, 529 insertions(+), 229 deletions(-) diff --git a/CHANGELOG_DRAFT.md b/CHANGELOG_DRAFT.md index d29c6a4..7710678 100644 --- a/CHANGELOG_DRAFT.md +++ b/CHANGELOG_DRAFT.md @@ -1,13 +1,19 @@ ## [NEXT] -## [2.1.8] — 2026-04-24 +## [2.1.8] — 2026-04-28 - Optimized network synchronization with deterministic tile slots and granular board sync - Removed lag-sensitive server adjacency checks to fix "disappearing tiles" on high-latency connections - Fixed "No multiplayer peer assigned" crash during host disconnection and cleanup - Finalized AP system removal, transitioning to a fully real-time authoritative model - Restored bot mission completion logic - Stabilized Nakama socket cleanup during match-to-lobby transitions - +- Fixed Tekton not rotating toward spawning direction when throwing tiles +- Fixed purple powerup tiles spawning on top of existing layer 1 tiles +- Added animation delay to tile spawning to match Tekton throw animation timing +- Implemented 1-tile perimeter buffer on all spawn areas to prevent edge spawning and stuck gaps +- Tekton NPCs now spawn exclusively in corner zones; players spawn in middle zones +- Fixed outline shader being lost when applying skin material overrides +- Added Unstuck button to pause menu to teleport stuck players to a safe area ## [2.1.7] — 2026-04-24 - Upgraded Gacha interface with dynamic CSGO-style sequential reveal animations diff --git a/assets/data/version.json b/assets/data/version.json index b931d17..57a590a 100644 --- a/assets/data/version.json +++ b/assets/data/version.json @@ -1,7 +1,20 @@ { - "latest_version": "2.1.7", + "latest_version": "2.1.8", "minimum_app_version": "2.1.0", "releases": [ + { + "version": "2.1.8", + "date": "2026-04-28", + "pck_url": "https://raw.githubusercontent.com/adtpdn/tekton-updates/main/latest/patch.pck", + "pck_size": 0, + "changelog": [ + "Fixed Tekton rotation and tile spawning animation timing", + "Implemented 1-tile perimeter buffer for all spawn areas to prevent stuck gaps", + "Tekton NPCs now spawn in corner zones; players spawn in middle zones", + "Fixed outline shader being lost when applying skin material overrides", + "Added Unstuck button to pause menu to teleport stuck players to a safe area" + ] + }, { "version": "2.1.7", "date": "2026-04-24", diff --git a/scenes/main.gd b/scenes/main.gd index dcdf01a..203163f 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -21,6 +21,8 @@ var vfx_manager # Minimal local state var _connection_check_timer: float = 0.0 var reserved_static_positions: Array[Vector2i] = [] +var _unstuck_cooldown_remaining: float = 0.0 +const UNSTUCK_COOLDOWN = 120.0 # 2 minutes func _can_rpc() -> bool: if not is_inside_tree(): return false @@ -523,6 +525,19 @@ func _process(delta): if not is_inside_tree(): return if not check_multiplayer(): return + # Tick down unstuck cooldown and update button label + if _unstuck_cooldown_remaining > 0.0: + _unstuck_cooldown_remaining -= delta + var unstuck_btn = get_node_or_null("PauseMenu/Panel/VBox/UnstuckBtn") + if unstuck_btn: + if _unstuck_cooldown_remaining > 0.0: + unstuck_btn.text = "Unstuck (%ds)" % ceil(_unstuck_cooldown_remaining) + unstuck_btn.disabled = true + else: + _unstuck_cooldown_remaining = 0.0 + unstuck_btn.text = "Unstuck" + unstuck_btn.disabled = false + if ui_manager and get_tree(): var all_players = get_tree().get_nodes_in_group("Players") if all_players.size() > 0: @@ -849,17 +864,91 @@ func _start_game(): spawn_tekton_npc() +# ============================================================================= +# Spawn Zone System - Prevents edge gaps with 1-tile perimeter buffer +# ============================================================================= + +const PERIMETER_BUFFER = 1 # 1-tile safe zone on all sides + +enum SpawnZone { + TOP_LEFT_CORNER, + TOP_CENTER, + TOP_RIGHT_CORNER, + MIDDLE_LEFT, + MIDDLE_CENTER, + MIDDLE_RIGHT, + BOTTOM_LEFT_CORNER, + BOTTOM_CENTER, + BOTTOM_RIGHT_CORNER +} + +func _get_spawn_zones(gridmap: Node) -> Dictionary: + """Returns a dictionary of spawn zones based on 3x3 grid layout. + Corner zones are for Tektons, middle zones are for players.""" + var width = gridmap.columns + var height = gridmap.rows + + # Apply perimeter buffer + var safe_width = width - (PERIMETER_BUFFER * 2) + var safe_height = height - (PERIMETER_BUFFER * 2) + + # Divide safe area into 3x3 grid + var zone_w = safe_width / 3 + var zone_h = safe_height / 3 + + var zones = {} + + # Define 9 zones with buffer offsets + for row in range(3): + for col in range(3): + var zone_idx = row * 3 + col + var zone_rect = Rect2i( + PERIMETER_BUFFER + (col * zone_w), + PERIMETER_BUFFER + (row * zone_h), + zone_w, + zone_h + ) + zones[zone_idx] = zone_rect + + return zones + +func _is_position_in_zone(pos: Vector2i, zone: Rect2i) -> bool: + """Check if position is within a spawn zone.""" + return zone.has_point(pos) + +func _get_tekton_spawn_zones(zones: Dictionary) -> Array: + """Returns corner zones for Tekton spawning.""" + return [ + zones[SpawnZone.TOP_LEFT_CORNER], + zones[SpawnZone.TOP_RIGHT_CORNER], + zones[SpawnZone.BOTTOM_LEFT_CORNER], + zones[SpawnZone.BOTTOM_RIGHT_CORNER] + ] + +func _get_player_spawn_zones(zones: Dictionary) -> Array: + """Returns middle zones for player spawning.""" + return [ + zones[SpawnZone.TOP_CENTER], + zones[SpawnZone.MIDDLE_LEFT], + zones[SpawnZone.MIDDLE_CENTER], + zones[SpawnZone.MIDDLE_RIGHT], + zones[SpawnZone.BOTTOM_CENTER] + ] + func _assign_random_spawn_positions(): - """Assign spawn positions distributed to 4 corners (2 per corner for 8 players).""" + """Assign spawn positions distributed across middle zones (avoiding corners reserved for Tektons).""" var enhanced_gridmap = $EnhancedGridMap if not enhanced_gridmap: return - # Lists for each quadrant - var spawns_TL = [] # Top-Left - var spawns_TR = [] # Top-Right - var spawns_BL = [] # Bottom-Left - var spawns_BR = [] # Bottom-Right + # Get spawn zones with perimeter buffer + var spawn_zones = _get_spawn_zones(enhanced_gridmap) + var player_zones = _get_player_spawn_zones(spawn_zones) + + # Lists for player spawns in each zone + var spawns_by_zone = {} # zone_rect -> [positions] + for zone in player_zones: + spawns_by_zone[zone] = [] var all_spawns = [] # Fallback # Stop n Go Custom Spawn Logic @@ -874,23 +963,20 @@ func _assign_random_spawn_positions(): _assign_portal_mode_spawn_positions(all_players) return - var mid_x = enhanced_gridmap.columns / 2 - var mid_z = enhanced_gridmap.rows / 2 - # If static positions were not calculated yet, do it now to avoid players spawning in them if reserved_static_positions.is_empty() and LobbyManager.game_mode != "Stop n Go": if not static_tekton_manager: static_tekton_manager = preload("res://scripts/managers/static_tekton_manager.gd").new() reserved_static_positions = static_tekton_manager.calculate_spawn_points(3, enhanced_gridmap) - for x in range(enhanced_gridmap.columns): - for z in range(enhanced_gridmap.rows): + # Scan grid for walkable positions within player zones (respecting buffer) + for x in range(PERIMETER_BUFFER, enhanced_gridmap.columns - PERIMETER_BUFFER): + for z in range(PERIMETER_BUFFER, enhanced_gridmap.rows - PERIMETER_BUFFER): var ground = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) if ground == 0: # Walkable var pos = Vector2i(x, z) # SAFETY CHECK: Is this reserved for a Static Tekton Stand? - # Stand clears exactly 3x3 area var is_safe = true for reserved in reserved_static_positions: if abs(x - reserved.x) <= 1 and abs(z - reserved.y) <= 1: @@ -900,32 +986,21 @@ func _assign_random_spawn_positions(): if not is_safe: continue - all_spawns.append(pos) + # Check if position is in any player zone + var in_player_zone = false + for zone in player_zones: + if _is_position_in_zone(pos, zone): + spawns_by_zone[zone].append(pos) + in_player_zone = true + break - if x < mid_x and z < mid_z: - spawns_TL.append(pos) - elif x >= mid_x and z < mid_z: - spawns_TR.append(pos) - elif x < mid_x and z >= mid_z: - spawns_BL.append(pos) - else: - spawns_BR.append(pos) + # Add to fallback list regardless of zone + if in_player_zone: + all_spawns.append(pos) - # Sort lists by distance to corners (closest to corner should be last, to be popped first) - # TL: Close to (0,0) -> Sort descending distance (so closest is at end) - spawns_TL.sort_custom(func(a, b): return a.length_squared() > b.length_squared()) - - # TR: Close to (13, 0) - var tr_corner = Vector2i(enhanced_gridmap.columns - 1, 0) - spawns_TR.sort_custom(func(a, b): return a.distance_squared_to(tr_corner) > b.distance_squared_to(tr_corner)) - - # BL: Close to (0, 13) - var bl_corner = Vector2i(0, enhanced_gridmap.rows - 1) - spawns_BL.sort_custom(func(a, b): return a.distance_squared_to(bl_corner) > b.distance_squared_to(bl_corner)) - - # BR: Close to (13, 13) - var br_corner = Vector2i(enhanced_gridmap.columns - 1, enhanced_gridmap.rows - 1) - spawns_BR.sort_custom(func(a, b): return a.distance_squared_to(br_corner) > b.distance_squared_to(br_corner)) + # Shuffle each zone's spawn list for randomization + for zone in spawns_by_zone: + spawns_by_zone[zone].shuffle() # Fallback shuffle all_spawns.shuffle() @@ -936,24 +1011,24 @@ func _assign_random_spawn_positions(): var spawn_index = 0 - # Round-robin assignment to corners: TL, TR, BR, BL, TL, TR, BR, BL... - # Order: TL -> TR -> BR -> BL (Clockwise-ish) - var quadrants = [spawns_TL, spawns_TR, spawns_BR, spawns_BL] + # Round-robin assignment across player zones + var zone_list = spawns_by_zone.keys() + var zone_arrays = spawns_by_zone.values() for player in all_players: var assigned_pos = Vector2i(-1, -1) - # Try to get from the current quadrant - var quadrant_idx = spawn_index % 4 - var quadrant = quadrants[quadrant_idx] + # Try to get from the current zone (round-robin) + var zone_idx = spawn_index % zone_arrays.size() + var zone_spawns = zone_arrays[zone_idx] - if quadrant.size() > 0: - assigned_pos = quadrant.pop_back() + if zone_spawns.size() > 0: + assigned_pos = zone_spawns.pop_back() else: - # Fallback: Try other quadrants if preferred one is empty - for q in quadrants: - if q.size() > 0: - assigned_pos = q.pop_back() + # Fallback: Try other zones if preferred one is empty + for zone_arr in zone_arrays: + if zone_arr.size() > 0: + assigned_pos = zone_arr.pop_back() break # Ultimate fallback: Random from anywhere @@ -1091,51 +1166,54 @@ const StaticTektonManager = preload("res://scripts/managers/static_tekton_manage var static_tekton_manager func spawn_tekton_npc(): - """Spawn a Tekton NPC at a random location.""" + """Spawn Tektons in corner zones only (avoiding player spawn areas).""" if not multiplayer.is_server(): return - # Find random valid position var enhanced_gridmap = $EnhancedGridMap if not enhanced_gridmap: return - # Spawn 3 Roaming Tektons - var spawned_count = 0 - var attempts = 0 + # Get corner zones for Tekton spawning + var spawn_zones = _get_spawn_zones(enhanced_gridmap) + var tekton_zones = _get_tekton_spawn_zones(spawn_zones) - while spawned_count < 3 and attempts < 50: - attempts += 1 + # Collect valid spawn positions in corner zones + var valid_positions = [] + for zone in tekton_zones: + for x in range(zone.position.x, zone.position.x + zone.size.x): + for y in range(zone.position.y, zone.position.y + zone.size.y): + var cell = Vector3i(x, 0, y) + if enhanced_gridmap.get_cell_item(cell) == 0: # Walkable floor + # Ensure not occupied by static tekton stand + var item_id = enhanced_gridmap.get_cell_item(Vector3i(x, 1, y)) + if item_id == 4: continue # Wall/Stand + + # Check RESERVED positions (static tekton stands) + var is_safe = true + for reserved in reserved_static_positions: + if abs(x - reserved.x) <= 1 and abs(y - reserved.y) <= 1: + is_safe = false + break + if not is_safe: continue + + valid_positions.append(Vector2i(x, y)) + + # Shuffle and spawn 3 Roaming Tektons + valid_positions.shuffle() + var spawned_count = 0 + + for pos in valid_positions: + if spawned_count >= 3: break - # Find random valid position - var valid_pos = Vector2i(-1, -1) - var x = randi() % enhanced_gridmap.columns - var y = randi() % enhanced_gridmap.rows - var cell = Vector3i(x, 0, y) + # Generate a consistent ID/Name for sync + var tekton_id = Time.get_ticks_msec() + spawned_count + _create_tekton(pos, tekton_id) - # Check if walkable and no existing Tekton nearby? - if enhanced_gridmap.get_cell_item(cell) == 0: # Walkable floor - # Ensure not occupied by static tekton stand (Item 4) - var item_id = enhanced_gridmap.get_cell_item(Vector3i(x, 1, y)) - if item_id == 4: continue # Wall/Stand - - # Also check RESERVED positions (if they haven't spawned yet or for safety) - var is_safe = true - for reserved in reserved_static_positions: - if abs(x - reserved.x) <= 1 and abs(y - reserved.y) <= 1: - is_safe = false - break - if not is_safe: continue - - valid_pos = Vector2i(x, y) - - # Generate a consistent ID/Name for sync (add index to ensure uniqueness) - var tekton_id = Time.get_ticks_msec() + spawned_count - _create_tekton(valid_pos, tekton_id) - # Only broadcast to clients if there are remote peers connected - if can_rpc() and multiplayer.get_peers().size() > 0: - rpc("sync_spawn_tekton", valid_pos, tekton_id) - - spawned_count += 1 - print("[Main] Spawned Tekton %d at %s" % [spawned_count, valid_pos]) + # Only broadcast to clients if there are remote peers connected + if can_rpc() and multiplayer.get_peers().size() > 0: + rpc("sync_spawn_tekton", pos, tekton_id) + + spawned_count += 1 + print("[Main] Spawned Tekton %d at %s (Corner Zone)" % [spawned_count, pos]) @rpc("call_remote", "reliable") func sync_spawn_tekton(pos: Vector2i, tekton_id: int): @@ -2527,6 +2605,110 @@ func _on_settings_back_pressed(): if pause_menu: pause_menu.visible = true +func _on_unstuck_pressed(): + """Teleport the local player to a safe spawn position when stuck.""" + if _unstuck_cooldown_remaining > 0.0: + print("[Unstuck] On cooldown: %.0fs remaining" % _unstuck_cooldown_remaining) + return + + var enhanced_gridmap = $EnhancedGridMap + if not enhanced_gridmap: + print("[Unstuck] No gridmap found") + return + + # Find the local player + var local_player = null + var all_players = get_tree().get_nodes_in_group("Players") + for player in all_players: + if player.is_multiplayer_authority(): + local_player = player + break + + if not local_player: + print("[Unstuck] No local player found") + return + + # Find a safe spawn position using the spawn zone system + var safe_pos = _find_safe_spawn_position(enhanced_gridmap, local_player) + if safe_pos == Vector2i(-1, -1): + print("[Unstuck] Failed to find safe position") + return + + # Teleport the player to the safe position + local_player.current_position = safe_pos + local_player.position = local_player.grid_to_world(safe_pos) + local_player.is_player_moving = false + + # Sync the new position to all clients + if local_player.has_method("rpc"): + local_player.rpc("sync_position", safe_pos) + + print("[Unstuck] Teleported player to safe position: %s" % safe_pos) + + # Start cooldown + _unstuck_cooldown_remaining = UNSTUCK_COOLDOWN + var unstuck_btn = get_node_or_null("PauseMenu/Panel/VBox/UnstuckBtn") + if unstuck_btn: + unstuck_btn.disabled = true + + # Close the pause menu + var pause_menu = get_node_or_null("PauseMenu") + if pause_menu: + pause_menu.visible = false + +func _find_safe_spawn_position(gridmap: Node, player: Node) -> Vector2i: + """Find a safe spawn position using the existing spawn zone system. + Prioritizes player zones but will fall back to any walkable position.""" + + # Get spawn zones with perimeter buffer + var spawn_zones = _get_spawn_zones(gridmap) + var player_zones = _get_player_spawn_zones(spawn_zones) + + # Collect valid positions from player zones + var valid_positions = [] + for zone in player_zones: + for x in range(zone.position.x, zone.position.x + zone.size.x): + for z in range(zone.position.y, zone.position.y + zone.size.y): + var cell = Vector3i(x, 0, z) + if gridmap.get_cell_item(cell) == 0: # Walkable floor + # Check for obstacles on layer 1 + var layer1_item = gridmap.get_cell_item(Vector3i(x, 1, z)) + if layer1_item == -1 or layer1_item in [7, 8, 9, 10, 11, 12, 13, 14]: # Empty or pickable tiles + # Check if position is not occupied by another player + var occupied = false + var all_players = get_tree().get_nodes_in_group("Players") + for p in all_players: + if p != player and p.current_position == Vector2i(x, z): + occupied = true + break + + if not occupied: + valid_positions.append(Vector2i(x, z)) + + # If we found valid positions, pick a random one + if valid_positions.size() > 0: + valid_positions.shuffle() + return valid_positions[0] + + # Fallback: Search the entire grid with buffer for ANY walkable position + for x in range(PERIMETER_BUFFER, gridmap.columns - PERIMETER_BUFFER): + for z in range(PERIMETER_BUFFER, gridmap.rows - PERIMETER_BUFFER): + var cell = Vector3i(x, 0, z) + if gridmap.get_cell_item(cell) == 0: # Walkable + var layer1_item = gridmap.get_cell_item(Vector3i(x, 1, z)) + if layer1_item == -1 or layer1_item in [7, 8, 9, 10, 11, 12, 13, 14]: + var occupied = false + var all_players = get_tree().get_nodes_in_group("Players") + for p in all_players: + if p != player and p.current_position == Vector2i(x, z): + occupied = true + break + if not occupied: + return Vector2i(x, z) + + # Ultimate fallback: center of the map + return Vector2i(gridmap.columns / 2, gridmap.rows / 2) + func _on_button_size_changed(value: float): if touch_controls: touch_controls.button_size = value diff --git a/scenes/main.tscn b/scenes/main.tscn index 183b94e..f6fd109 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1819,6 +1819,12 @@ layout_mode = 2 theme_override_fonts/font = ExtResource("13_j8jky") text = "Settings" +[node name="UnstuckBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=345678901] +custom_minimum_size = Vector2(200, 45) +layout_mode = 2 +theme_override_fonts/font = ExtResource("13_j8jky") +text = "Unstuck" + [node name="QuitBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=1771850243] custom_minimum_size = Vector2(200, 45) layout_mode = 2 @@ -2056,5 +2062,6 @@ flat = true [connection signal="pressed" from="PauseMenu/Panel/VBox/ResumeBtn" to="." method="_on_resume_pressed"] [connection signal="pressed" from="PauseMenu/Panel/VBox/HowToPlayBtn" to="." method="_on_how_to_play_pressed"] [connection signal="pressed" from="PauseMenu/Panel/VBox/SettingsBtn" to="." method="_on_settings_pressed"] +[connection signal="pressed" from="PauseMenu/Panel/VBox/UnstuckBtn" to="." method="_on_unstuck_pressed"] [connection signal="pressed" from="PauseMenu/Panel/VBox/QuitBtn" to="." method="_on_quit_match_pressed"] [connection signal="pressed" from="HowToPlayPanel/Panel/VBox/BackBtn" to="." method="_on_how_to_play_back_pressed"] diff --git a/scenes/player.gd b/scenes/player.gd index 27347e8..23e892c 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -595,6 +595,16 @@ func sync_loadout(loadout_data: Dictionary) -> void: _player_loadout = loadout_data # Pass 'self' as character_root — SkinManager will find $Oldpop/$Bob/etc. by name SkinManager.apply_loadout(self, _player_loadout) + + # Re-apply outline shader to ensure it's present after skin changes + var active_character: Node3D = null + match _selected_character: + "Bob": active_character = character_bob + "Masbro": active_character = character_masbro + "Gatot": active_character = character_gatot + "Oldpop": active_character = character_oldpop + if active_character: + _apply_outline_recursive(active_character) func _setup_character() -> void: """Initialize character based on LobbyManager selection or defaults.""" diff --git a/scenes/tools/skin_shader_generator.tscn b/scenes/tools/skin_shader_generator.tscn index c55d39d..770ceb5 100644 --- a/scenes/tools/skin_shader_generator.tscn +++ b/scenes/tools/skin_shader_generator.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://dbcyurv4m7mka" path="res://scripts/tools/skin_shader_generator.gd" id="1_script"] [ext_resource type="Texture2D" uid="uid://c1i00chwkmxtw" path="res://assets/characters/Oldpop_oldpop-tex.png" id="2_ac8h2"] -[node name="SkinShaderGenerator" type="Control"] +[node name="SkinShaderGenerator" type="Control" unique_id=1463212218] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -12,7 +12,7 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_script") -[node name="MainHSplit" type="HSplitContainer" parent="."] +[node name="MainHSplit" type="HSplitContainer" parent="." unique_id=1205691437] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -26,61 +26,61 @@ grow_vertical = 2 split_offsets = PackedInt32Array(480) split_offset = 480 -[node name="UVEditorVBox" type="VBoxContainer" parent="MainHSplit"] +[node name="UVEditorVBox" type="VBoxContainer" parent="MainHSplit" unique_id=1641474519] custom_minimum_size = Vector2(480, 0) layout_mode = 2 -[node name="UVPanel" type="PanelContainer" parent="MainHSplit/UVEditorVBox"] +[node name="UVPanel" type="PanelContainer" parent="MainHSplit/UVEditorVBox" unique_id=759141139] layout_mode = 2 size_flags_vertical = 3 -[node name="UVPreview" type="TextureRect" parent="MainHSplit/UVEditorVBox/UVPanel"] +[node name="UVPreview" type="TextureRect" parent="MainHSplit/UVEditorVBox/UVPanel" unique_id=1983575193] unique_name_in_owner = true layout_mode = 2 texture = ExtResource("2_ac8h2") expand_mode = 1 stretch_mode = 5 -[node name="UVOverlay" type="Control" parent="MainHSplit/UVEditorVBox/UVPanel"] +[node name="UVOverlay" type="Control" parent="MainHSplit/UVEditorVBox/UVPanel" unique_id=1992150626] unique_name_in_owner = true layout_mode = 2 -[node name="UVToolsHBox" type="HBoxContainer" parent="MainHSplit/UVEditorVBox"] +[node name="UVToolsHBox" type="HBoxContainer" parent="MainHSplit/UVEditorVBox" unique_id=875713341] custom_minimum_size = Vector2(0, 28) layout_mode = 2 -[node name="ShowAllUVs" type="CheckBox" parent="MainHSplit/UVEditorVBox/UVToolsHBox"] +[node name="ShowAllUVs" type="CheckBox" parent="MainHSplit/UVEditorVBox/UVToolsHBox" unique_id=924232453] unique_name_in_owner = true layout_mode = 2 text = "Show All UVs" -[node name="FlipUV" type="CheckBox" parent="MainHSplit/UVEditorVBox/UVToolsHBox"] +[node name="FlipUV" type="CheckBox" parent="MainHSplit/UVEditorVBox/UVToolsHBox" unique_id=486835238] unique_name_in_owner = true layout_mode = 2 text = "Flip UV" -[node name="HideRef" type="CheckBox" parent="MainHSplit/UVEditorVBox/UVToolsHBox"] +[node name="HideRef" type="CheckBox" parent="MainHSplit/UVEditorVBox/UVToolsHBox" unique_id=486814611] unique_name_in_owner = true layout_mode = 2 text = "Hide Ref" -[node name="RightHSplit" type="HSplitContainer" parent="MainHSplit"] +[node name="RightHSplit" type="HSplitContainer" parent="MainHSplit" unique_id=2005506586] layout_mode = 2 split_offsets = PackedInt32Array(400) split_offset = 400 -[node name="PreviewVBox" type="VBoxContainer" parent="MainHSplit/RightHSplit"] +[node name="PreviewVBox" type="VBoxContainer" parent="MainHSplit/RightHSplit" unique_id=1034615855] layout_mode = 2 -[node name="ViewportPanel" type="PanelContainer" parent="MainHSplit/RightHSplit/PreviewVBox"] +[node name="ViewportPanel" type="PanelContainer" parent="MainHSplit/RightHSplit/PreviewVBox" unique_id=778081482] layout_mode = 2 size_flags_vertical = 3 -[node name="ViewportContainer" type="SubViewportContainer" parent="MainHSplit/RightHSplit/PreviewVBox/ViewportPanel"] +[node name="ViewportContainer" type="SubViewportContainer" parent="MainHSplit/RightHSplit/PreviewVBox/ViewportPanel" unique_id=2067285271] layout_mode = 2 stretch = true -[node name="Viewport3D" type="SubViewport" parent="MainHSplit/RightHSplit/PreviewVBox/ViewportPanel/ViewportContainer"] +[node name="Viewport3D" type="SubViewport" parent="MainHSplit/RightHSplit/PreviewVBox/ViewportPanel/ViewportContainer" unique_id=319895518] unique_name_in_owner = true own_world_3d = true handle_input_locally = false @@ -88,15 +88,15 @@ msaa_3d = 2 size = Vector2i(400, 646) render_target_update_mode = 4 -[node name="ZoomHBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/PreviewVBox"] +[node name="ZoomHBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/PreviewVBox" unique_id=1494239181] layout_mode = 2 -[node name="ZoomMinus" type="Button" parent="MainHSplit/RightHSplit/PreviewVBox/ZoomHBox"] +[node name="ZoomMinus" type="Button" parent="MainHSplit/RightHSplit/PreviewVBox/ZoomHBox" unique_id=1909808732] unique_name_in_owner = true layout_mode = 2 text = " − " -[node name="ZoomSlider" type="HSlider" parent="MainHSplit/RightHSplit/PreviewVBox/ZoomHBox"] +[node name="ZoomSlider" type="HSlider" parent="MainHSplit/RightHSplit/PreviewVBox/ZoomHBox" unique_id=117912774] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 @@ -106,140 +106,150 @@ max_value = 10.0 step = 0.1 value = 1.8 -[node name="ZoomPlus" type="Button" parent="MainHSplit/RightHSplit/PreviewVBox/ZoomHBox"] +[node name="ZoomPlus" type="Button" parent="MainHSplit/RightHSplit/PreviewVBox/ZoomHBox" unique_id=1171788517] unique_name_in_owner = true layout_mode = 2 text = " + " -[node name="InspectedLabel" type="Label" parent="MainHSplit/RightHSplit/PreviewVBox"] +[node name="InspectedLabel" type="Label" parent="MainHSplit/RightHSplit/PreviewVBox" unique_id=1759338532] unique_name_in_owner = true layout_mode = 2 theme_override_colors/font_color = Color(1, 1, 0, 1) horizontal_alignment = 1 -[node name="TabContainer" type="TabContainer" parent="MainHSplit/RightHSplit"] +[node name="TabContainer" type="TabContainer" parent="MainHSplit/RightHSplit" unique_id=542984640] unique_name_in_owner = true custom_minimum_size = Vector2(360, 0) layout_mode = 2 current_tab = 1 -[node name="Scene Tree" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer"] +[node name="Scene Tree" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer" unique_id=899733231] visible = false layout_mode = 2 metadata/_tab_index = 0 -[node name="MeshList" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Scene Tree"] +[node name="MeshList" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Scene Tree" unique_id=150906488] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 -[node name="Inspector" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer"] +[node name="Inspector" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer" unique_id=748873039] layout_mode = 2 horizontal_scroll_mode = 0 metadata/_tab_index = 1 -[node name="PIPanel" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector"] +[node name="PIPanel" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector" unique_id=1353242101] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 12 -[node name="Margin" type="MarginContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel"] +[node name="Margin" type="MarginContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel" unique_id=718046840] layout_mode = 2 theme_override_constants/margin_left = 6 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 6 theme_override_constants/margin_bottom = 6 -[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin"] +[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin" unique_id=368414230] layout_mode = 2 theme_override_constants/separation = 12 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox" unique_id=1406164062] layout_mode = 2 theme_override_colors/font_color = Color(0, 1, 1, 1) text = "▼ Part Properties" -[node name="PIName" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox"] +[node name="PIName" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox" unique_id=174857139] unique_name_in_owner = true layout_mode = 2 theme_override_colors/font_color = Color(1, 1, 0, 1) text = "(no part selected)" -[node name="Grid" type="GridContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox"] +[node name="Grid" type="GridContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox" unique_id=68655076] layout_mode = 2 columns = 2 -[node name="Label4" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="Label4" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=455538616] layout_mode = 2 text = "Ref UV Img:" -[node name="PIRefHBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="PIRefHBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=1269364062] layout_mode = 2 -[node name="PIRefBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIRefHBox"] +[node name="PIRefBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIRefHBox" unique_id=1975704724] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 text = "Default" text_overrun_behavior = 3 -[node name="PIRefClear" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIRefHBox"] +[node name="PIRefClear" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIRefHBox" unique_id=1144899648] unique_name_in_owner = true layout_mode = 2 text = "✕" -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=1210824002] layout_mode = 2 text = "Category:" -[node name="PICategory" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="PICategory" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=1287709635] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +selected = 0 +item_count = 4 +popup/item_0/text = "None / Skin" +popup/item_0/id = 0 +popup/item_1/text = "Hat" +popup/item_1/id = 0 +popup/item_2/text = "Gloves" +popup/item_2/id = 1 +popup/item_3/text = "Cloth" +popup/item_3/id = 2 -[node name="Label2" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="Label2" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=667580391] layout_mode = 2 text = "Fill Color:" -[node name="PIColor" type="ColorPickerButton" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="PIColor" type="ColorPickerButton" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=1375411324] unique_name_in_owner = true custom_minimum_size = Vector2(60, 0) layout_mode = 2 color = Color(1, 1, 1, 1) -[node name="Label3" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="Label3" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=269090273] layout_mode = 2 text = "Sticker Img:" -[node name="PIImageHBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid"] +[node name="PIImageHBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid" unique_id=982037215] layout_mode = 2 -[node name="PIImageBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIImageHBox"] +[node name="PIImageBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIImageHBox" unique_id=710574420] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 text = "None" text_overrun_behavior = 3 -[node name="PIImageClear" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIImageHBox"] +[node name="PIImageClear" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/Grid/PIImageHBox" unique_id=579810771] unique_name_in_owner = true layout_mode = 2 text = "✕" -[node name="TransformVBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox"] +[node name="TransformVBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox" unique_id=1554876686] unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 8 -[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox"] +[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox" unique_id=759048628] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox" unique_id=1162724878] layout_mode = 2 text = "Pos X:" -[node name="PIPosX" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox"] +[node name="PIPosX" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox" unique_id=1858913695] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 @@ -247,14 +257,14 @@ min_value = -1.0 max_value = 2.0 step = 0.01 -[node name="HBox2" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox"] +[node name="HBox2" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox" unique_id=1136376502] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox2"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox2" unique_id=171517440] layout_mode = 2 text = "Pos Y:" -[node name="PIPosY" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox2"] +[node name="PIPosY" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox2" unique_id=419957765] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 @@ -262,14 +272,14 @@ min_value = -1.0 max_value = 2.0 step = 0.01 -[node name="HBox3" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox"] +[node name="HBox3" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox" unique_id=191899165] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox3"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox3" unique_id=1617628574] layout_mode = 2 text = "Scale:" -[node name="PIScale" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox3"] +[node name="PIScale" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox3" unique_id=1132682974] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 @@ -278,155 +288,174 @@ max_value = 5.0 step = 0.01 value = 1.0 -[node name="HBox4" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox"] +[node name="HBox4" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox" unique_id=1943002195] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox4"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox4" unique_id=1211717333] layout_mode = 2 text = "Rotate:" -[node name="PIRot" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox4"] +[node name="PIRot" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/TransformVBox/HBox4" unique_id=2006377715] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 min_value = -180.0 max_value = 180.0 -[node name="HSeparator" type="HSeparator" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox"] +[node name="HSeparator" type="HSeparator" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox" unique_id=208929617] layout_mode = 2 -[node name="MaskSection" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox"] +[node name="MaskSection" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox" unique_id=1009501329] layout_mode = 2 theme_override_constants/separation = 8 -[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection"] +[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection" unique_id=2142764592] layout_mode = 2 -[node name="Label2" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/HBox"] +[node name="Label2" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/HBox" unique_id=1750314177] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(1, 1, 0, 1) text = "UV Masks (isolate regions):" -[node name="AddMaskBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/HBox"] +[node name="AddMaskBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/HBox" unique_id=841819147] unique_name_in_owner = true layout_mode = 2 text = "+ Add" -[node name="MaskList" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection"] +[node name="MaskList" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection" unique_id=396917805] unique_name_in_owner = true layout_mode = 2 -[node name="DrawingToolbar" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection"] +[node name="DrawingToolbar" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection" unique_id=1024349553] unique_name_in_owner = true visible = false layout_mode = 2 -[node name="DrawStatus" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/DrawingToolbar"] +[node name="DrawStatus" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/DrawingToolbar" unique_id=1815443043] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 text = "Editing Mask A..." -[node name="FinishBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/DrawingToolbar"] +[node name="FinishBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/DrawingToolbar" unique_id=1820527087] unique_name_in_owner = true layout_mode = 2 text = "✓ Done" -[node name="ClearBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/DrawingToolbar"] +[node name="ClearBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Inspector/PIPanel/Margin/VBox/MaskSection/DrawingToolbar" unique_id=1393501427] unique_name_in_owner = true layout_mode = 2 text = "✕ Clear" -[node name="Settings" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer"] +[node name="Settings" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer" unique_id=602338965] visible = false layout_mode = 2 horizontal_scroll_mode = 0 metadata/_tab_index = 2 -[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings"] +[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings" unique_id=45543798] layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 10 -[node name="Margin" type="MarginContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox"] +[node name="Margin" type="MarginContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox" unique_id=556399693] layout_mode = 2 theme_override_constants/margin_left = 6 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 6 theme_override_constants/margin_bottom = 6 -[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin"] +[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin" unique_id=1455447429] layout_mode = 2 theme_override_constants/separation = 12 -[node name="CharBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox"] +[node name="CharBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox" unique_id=668836559] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/CharBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/CharBox" unique_id=1834665707] layout_mode = 2 theme_override_colors/font_color = Color(0.5, 1, 1, 1) text = "Active Character:" -[node name="CharacterOpt" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/CharBox"] +[node name="CharacterOpt" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/CharBox" unique_id=1040984097] unique_name_in_owner = true layout_mode = 2 +selected = 0 +item_count = 4 +popup/item_0/text = "Oldpop" +popup/item_0/id = 0 +popup/item_1/text = "Masbro" +popup/item_1/id = 1 +popup/item_2/text = "Bob" +popup/item_2/id = 2 +popup/item_3/text = "Gatot" +popup/item_3/id = 3 -[node name="WireBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox"] +[node name="WireBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox" unique_id=1106179936] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox" unique_id=1522414156] layout_mode = 2 theme_override_colors/font_color = Color(0.5, 1, 1, 1) text = "Wireframe Opacity:" -[node name="WireOpacity" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox"] +[node name="WireOpacity" type="HSlider" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox" unique_id=2084209816] unique_name_in_owner = true layout_mode = 2 max_value = 1.0 step = 0.05 -value = 0.07 +value = 0.05 -[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox"] +[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox" unique_id=552477298] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox/HBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox/HBox" unique_id=1111725652] layout_mode = 2 text = "Color:" -[node name="WireColor" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox/HBox"] +[node name="WireColor" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/WireBox/HBox" unique_id=1491986963] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 selected = 0 +item_count = 4 +popup/item_0/text = "Black" +popup/item_0/id = 0 +popup/item_1/text = "White" +popup/item_1/id = 1 +popup/item_2/text = "Yellow" +popup/item_2/id = 2 +popup/item_3/text = "Green" +popup/item_3/id = 3 -[node name="HSeparator" type="HSeparator" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox"] +[node name="HSeparator" type="HSeparator" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox" unique_id=426048616] layout_mode = 2 -[node name="PresetBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox"] +[node name="PresetBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox" unique_id=12323314] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox" unique_id=744742837] layout_mode = 2 theme_override_colors/font_color = Color(0.5, 1, 1, 1) text = "Session Presets:" -[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox"] +[node name="HBox" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox" unique_id=327547742] layout_mode = 2 -[node name="SavePresetBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox/HBox"] +[node name="SavePresetBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox/HBox" unique_id=1945639309] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 text = "💾 Save JSON" -[node name="LoadPresetBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox/HBox"] +[node name="LoadPresetBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox/PresetBox/HBox" unique_id=1206320620] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 text = "📂 Load JSON" -[node name="Label2" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox"] +[node name="Label2" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Settings/VBox/Margin/VBox" unique_id=1382080014] layout_mode = 2 theme_override_colors/font_color = Color(0.5, 0.5, 0.5, 1) theme_override_font_sizes/font_size = 12 @@ -437,33 +466,33 @@ text = "Workflow: 4. Hit GENERATE to bake." autowrap_mode = 3 -[node name="Export" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer"] +[node name="Export" type="ScrollContainer" parent="MainHSplit/RightHSplit/TabContainer" unique_id=19499909] visible = false layout_mode = 2 horizontal_scroll_mode = 0 metadata/_tab_index = 3 -[node name="ExportVBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export"] +[node name="ExportVBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export" unique_id=793639475] layout_mode = 2 size_flags_horizontal = 3 -[node name="Margin" type="MarginContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox"] +[node name="Margin" type="MarginContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox" unique_id=1909064484] layout_mode = 2 theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 -[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin"] +[node name="VBox" type="VBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin" unique_id=1822993310] layout_mode = 2 theme_override_constants/separation = 12 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=1218142446] layout_mode = 2 theme_override_colors/font_color = Color(0, 1, 0.5, 1) text = "▼ Export Assets" -[node name="PrefixLabel" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="PrefixLabel" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=1416817408] unique_name_in_owner = true layout_mode = 2 theme_override_colors/font_color = Color(0, 1, 0.5, 1) @@ -471,50 +500,66 @@ theme_override_font_sizes/font_size = 13 text = "oldpop_skin_" horizontal_alignment = 2 -[node name="HBoxContainer" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="HBoxContainer" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=2142158910] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer" unique_id=1163691924] layout_mode = 2 text = "Name:" -[node name="ExportName" type="LineEdit" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer"] +[node name="ExportName" type="LineEdit" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer" unique_id=712721669] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 placeholder_text = "custom_name" -[node name="HBoxContainer2" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="HBoxContainer2" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=814721742] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer2"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer2" unique_id=1413953937] layout_mode = 2 text = "Target Folder:" -[node name="ExportFolder" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer2"] +[node name="ExportFolder" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer2" unique_id=1025584347] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +selected = 3 +item_count = 4 +popup/item_0/text = "Clothing" +popup/item_0/id = 0 +popup/item_1/text = "Gloves" +popup/item_1/id = 1 +popup/item_2/text = "Hat" +popup/item_2/id = 2 +popup/item_3/text = "General / Tex" +popup/item_3/id = 3 -[node name="HBoxContainer3" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="HBoxContainer3" type="HBoxContainer" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=839646285] layout_mode = 2 -[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer3"] +[node name="Label" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer3" unique_id=178279817] layout_mode = 2 text = "Mode:" -[node name="ExportMode" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer3"] +[node name="ExportMode" type="OptionButton" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox/HBoxContainer3" unique_id=1343098213] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +selected = 0 +item_count = 2 +popup/item_0/text = "Texture Only (.png)" +popup/item_0/id = 0 +popup/item_1/text = "Texture + Material" +popup/item_1/id = 1 -[node name="GenerateBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="GenerateBtn" type="Button" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=430835639] unique_name_in_owner = true custom_minimum_size = Vector2(0, 44) layout_mode = 2 text = "🚀 GENERATE & EXPORT" -[node name="StatusLabel" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox"] +[node name="StatusLabel" type="Label" parent="MainHSplit/RightHSplit/TabContainer/Export/ExportVBox/Margin/VBox" unique_id=1899608283] unique_name_in_owner = true layout_mode = 2 horizontal_alignment = 1 diff --git a/scripts/managers/skin_manager.gd b/scripts/managers/skin_manager.gd index 8eed902..19d5b4d 100644 --- a/scripts/managers/skin_manager.gd +++ b/scripts/managers/skin_manager.gd @@ -242,7 +242,18 @@ func _apply_skin_data(character_root: Node3D, skin: Dictionary) -> void: if mode == "overlay": mn.material_overlay = mat else: - mn.set_surface_override_material(0, mat) + # PRESERVE OUTLINE SHADER: Check if existing material has a next_pass (outline shader) + var existing_mat = mn.get_surface_override_material(0) + var preserved_next_pass = null + if existing_mat and existing_mat.next_pass: + preserved_next_pass = existing_mat.next_pass + + # Apply the skin material + var skin_mat = mat.duplicate() if mat else null + if skin_mat and preserved_next_pass: + skin_mat.next_pass = preserved_next_pass + + mn.set_surface_override_material(0, skin_mat) func _clear_slot(char_node: Node3D, slot: Dictionary) -> void: @@ -254,7 +265,20 @@ func _clear_slot(char_node: Node3D, slot: Dictionary) -> void: if mode == "overlay": mn.material_overlay = null else: - mn.set_surface_override_material(0, null) + # PRESERVE OUTLINE SHADER: When clearing, check if we need to restore outline + var existing_mat = mn.get_surface_override_material(0) + if existing_mat and existing_mat.next_pass: + # Has outline - restore base material with outline preserved + var base_mat = mn.get_active_material(0) + if base_mat: + var restored_mat = base_mat.duplicate() + restored_mat.next_pass = existing_mat.next_pass + mn.set_surface_override_material(0, restored_mat) + else: + # No base material, just clear but this shouldn't happen normally + mn.set_surface_override_material(0, null) + else: + mn.set_surface_override_material(0, null) func _get_char_node(character_root: Node3D, char_name: String) -> Node3D: diff --git a/scripts/managers/static_tekton_manager.gd b/scripts/managers/static_tekton_manager.gd index 3f97dd5..58ce5b5 100644 --- a/scripts/managers/static_tekton_manager.gd +++ b/scripts/managers/static_tekton_manager.gd @@ -6,6 +6,7 @@ extends Node const STAND_SCENE_PATH = "res://scenes/static_tekton_stand.tscn" const TEKTON_SCENE_PATH = "res://scenes/tekton.tscn" const STATIC_CONTROLLER_SCRIPT = "res://scripts/static_tekton_controller.gd" +const PERIMETER_BUFFER = 1 # 1-tile safe zone on all sides (matches main.gd) # Zone Definitions based on CameraContextManager logic # 9 Zones in a 3x3 grid (approximate for 14x14 map) @@ -16,7 +17,8 @@ const STATIC_CONTROLLER_SCRIPT = "res://scripts/static_tekton_controller.gd" func calculate_spawn_points(_count: int, gridmap: Node) -> Array[Vector2i]: """ Calculates the 5 fixed potential spawn positions for static tektons. - Corners: (1,1), (W-2,1), (1,H-2), (W-2,H-2) + Now respects 1-tile perimeter buffer to prevent edge spawning. + Corners: (2,2), (W-3,2), (2,H-3), (W-3,H-3) Center: (W/2, H/2) Returns exactly 5 spots if possible. """ @@ -25,15 +27,15 @@ func calculate_spawn_points(_count: int, gridmap: Node) -> Array[Vector2i]: var width = gridmap.columns var height = gridmap.rows - print("[StaticTektonManager] Calculating static tekton positions (Fixed 5-Spots)...") + print("[StaticTektonManager] Calculating static tekton positions (Fixed 5-Spots with Buffer)...") - # Fixed Spots + # Fixed Spots with buffer (at least 2 tiles from edge for 3x3 stand + 1 tile buffer) var spots: Array[Vector2i] = [ - Vector2i(1, 1), # Top-Left - Vector2i(width - 2, 1), # Top-Right - Vector2i(1, height - 2), # Bottom-Left - Vector2i(width - 2, height - 2), # Bottom-Right - Vector2i(width / 2, height / 2) # Center + Vector2i(PERIMETER_BUFFER + 1, PERIMETER_BUFFER + 1), # Top-Left + Vector2i(width - PERIMETER_BUFFER - 2, PERIMETER_BUFFER + 1), # Top-Right + Vector2i(PERIMETER_BUFFER + 1, height - PERIMETER_BUFFER - 2), # Bottom-Left + Vector2i(width - PERIMETER_BUFFER - 2, height - PERIMETER_BUFFER - 2), # Bottom-Right + Vector2i(width / 2, height / 2) # Center ] # Validate spots (ensure they are walkable and have room) @@ -52,18 +54,17 @@ func _pick_spot_in_zone_biased(zone: Rect2i, gridmap: Node, type: int) -> Vector # ideal target relative to map var target = Vector2i.ZERO match type: - 0: target = Vector2i(0, 0) - 1: target = Vector2i(gridmap.columns, 0) - 2: target = Vector2i(0, gridmap.rows) - 3: target = Vector2i(gridmap.columns, gridmap.rows) + 0: target = Vector2i(PERIMETER_BUFFER + 1, PERIMETER_BUFFER + 1) + 1: target = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, PERIMETER_BUFFER + 1) + 2: target = Vector2i(PERIMETER_BUFFER + 1, gridmap.rows - PERIMETER_BUFFER - 2) + 3: target = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, gridmap.rows - PERIMETER_BUFFER - 2) 4: target = Vector2i(gridmap.columns / 2, gridmap.rows / 2) - # Clamp target to be inside valid area (taking 3x3 margin into account) - # Center of 3x3 must be at least 1 tile from edge - var min_x = max(1, zone.position.x + 1) - var max_x = min(gridmap.columns - 2, zone.position.x + zone.size.x - 2) - var min_y = max(1, zone.position.y + 1) - var max_y = min(gridmap.rows - 2, zone.position.y + zone.size.y - 2) + # Clamp target to be inside valid area (respecting perimeter buffer + 3x3 margin) + var min_x = max(PERIMETER_BUFFER + 1, zone.position.x + 1) + var max_x = min(gridmap.columns - PERIMETER_BUFFER - 2, zone.position.x + zone.size.x - 2) + var min_y = max(PERIMETER_BUFFER + 1, zone.position.y + 1) + var max_y = min(gridmap.rows - PERIMETER_BUFFER - 2, zone.position.y + zone.size.y - 2) if min_x > max_x or min_y > max_y: return Vector2i(-1, -1) @@ -111,33 +112,32 @@ func _pick_spot_in_zone(zone: Rect2i, gridmap: Node, zone_idx: int = -1) -> Vect """ Find a valid 3x3 spot in the zone. The returned position is the CENTER of the 3x3 area. - If zone_idx is a corner (0, 2, 6, 8), we snap to the absolute map corner. + If zone_idx is a corner (0, 2, 6, 8), we snap to corner position with buffer. """ - # CORNER SNAPPING: If this is a corner zone, force it to the extreme corner - # to ensure the 3x3 Stand fills the corner completely (no 1-tile gaps). + # CORNER SNAPPING: If this is a corner zone, force it to the corner with buffer + # to ensure the 3x3 Stand stays away from edges (no 1-tile gaps). if zone_idx == 0: # Top-Left - var center = Vector2i(1, 1) + var center = Vector2i(PERIMETER_BUFFER + 1, PERIMETER_BUFFER + 1) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 2: # Top-Right - var center = Vector2i(gridmap.columns - 2, 1) + var center = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, PERIMETER_BUFFER + 1) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 6: # Bottom-Left - var center = Vector2i(1, gridmap.rows - 2) + var center = Vector2i(PERIMETER_BUFFER + 1, gridmap.rows - PERIMETER_BUFFER - 2) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 8: # Bottom-Right - var center = Vector2i(gridmap.columns - 2, gridmap.rows - 2) + var center = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, gridmap.rows - PERIMETER_BUFFER - 2) if _is_valid_3x3(center, gridmap): return center # Fallback/Random logic for non-corner zones or if preferred corner was invalid var attempts = 0 while attempts < 30: attempts += 1 - # Ensure center is at least 1 tile away from edges of the map to fit 3x3 - # zone.position might be 0,0. - var min_x = max(1, zone.position.x + 1) - var max_x = min(gridmap.columns - 2, zone.position.x + zone.size.x - 2) - var min_y = max(1, zone.position.y + 1) - var max_y = min(gridmap.rows - 2, zone.position.y + zone.size.y - 2) + # Ensure center respects perimeter buffer (1 tile from edge + 1 tile for 3x3 center = 2 tiles) + var min_x = max(PERIMETER_BUFFER + 1, zone.position.x + 1) + var max_x = min(gridmap.columns - PERIMETER_BUFFER - 2, zone.position.x + zone.size.x - 2) + var min_y = max(PERIMETER_BUFFER + 1, zone.position.y + 1) + var max_y = min(gridmap.rows - PERIMETER_BUFFER - 2, zone.position.y + zone.size.y - 2) if min_x > max_x or min_y > max_y: break # Zone too small diff --git a/scripts/tekton.gd b/scripts/tekton.gd index 8e37334..f052d3a 100644 --- a/scripts/tekton.gd +++ b/scripts/tekton.gd @@ -407,6 +407,15 @@ func spawn_tiles_around(count: int = 4): """Spawns a mix of normal and special tiles in a radius.""" if not enhanced_gridmap: return + var radius = 2 + var rng = RandomNumberGenerator.new() + rng.randomize() + + # FIX 1: Make tekton look/rotate toward a random spawning direction + if not is_carried and not is_thrown: + var random_angle = rng.randf_range(0, TAU) + rotation.y = random_angle + # Play throw animation if not is_carried and not is_thrown: play_animation("tekton_throw_tile") @@ -416,12 +425,11 @@ func spawn_tiles_around(count: int = 4): play_animation("tekton_idle") ) - var radius = 2 - var rng = RandomNumberGenerator.new() - rng.randomize() - print("[Tekton] Spawning %d tiles around %s" % [count, current_position]) + # FIX 3: Delay tile spawning to match throw animation timing (feels better) + await get_tree().create_timer(0.4).timeout + var spawned = 0 var attempts = 0 while spawned < count and attempts < 25: @@ -448,7 +456,12 @@ func spawn_tiles_around(count: int = 4): if floor_0_item in [4, -1]: continue - + + # FIX 2: Check if layer 1 already has tiles (purple powerups or any tile) to prevent blocking + var floor_1_item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 1, pos.y)) + if floor_1_item != -1: + continue # Skip if there's already a tile on layer 1 + # 50% chance to spawn something if rng.randf() > 0.5: continue