diff --git a/addons/enhanced_gridmap/meshlibrary/default.tres b/addons/enhanced_gridmap/meshlibrary/default.tres index f0bf1d9..dafe4ca 100644 --- a/addons/enhanced_gridmap/meshlibrary/default.tres +++ b/addons/enhanced_gridmap/meshlibrary/default.tres @@ -6,7 +6,7 @@ [ext_resource type="ArrayMesh" uid="uid://brevl3ab0tdqe" path="res://assets/models/tiles/tile_wall.tres" id="4_8v5xv"] [ext_resource type="ArrayMesh" uid="uid://b5ta7tcw0iscd" path="res://assets/models/tiles/tile_coin.tres" id="4_76xkl"] [ext_resource type="ArrayMesh" uid="uid://d4himvyb81in8" path="res://assets/models/meshes/non-walkable.res" id="4_sx8rm"] -[ext_resource type="ArrayMesh" uid="uid://dr80txgr61irt" path="res://assets/models/tiles/tile_area_freeze.tres" id="5_sx8rm"] +[ext_resource type="ArrayMesh" uid="uid://dqo83gvaay6sn" path="res://assets/models/tiles/tile_area_freeze.tres" id="5_sx8rm"] [ext_resource type="ArrayMesh" uid="uid://bqvqj3fhf5x51" path="res://assets/models/tiles/tile_ghost.tres" id="6_r32il"] [ext_resource type="ArrayMesh" uid="uid://cv4bedhida00g" path="res://assets/models/tiles/tile_star.tres" id="7_p5epg"] [ext_resource type="ArrayMesh" uid="uid://gpnl4cjrivor" path="res://assets/models/tiles/tile_speed.tres" id="7_sx8rm"] diff --git a/assets/graphics/touch_control/ghost.png b/assets/graphics/touch_control/ghost.png new file mode 100644 index 0000000..3e49be5 Binary files /dev/null and b/assets/graphics/touch_control/ghost.png differ diff --git a/assets/graphics/touch_control/ghost.png.import b/assets/graphics/touch_control/ghost.png.import new file mode 100644 index 0000000..7de7533 --- /dev/null +++ b/assets/graphics/touch_control/ghost.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2vhatfmufn3d" +path="res://.godot/imported/ghost.png-50b35ecd19732e2631cec7740daf0cec.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/touch_control/ghost.png" +dest_files=["res://.godot/imported/ghost.png-50b35ecd19732e2631cec7740daf0cec.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/models/tiles/tile_area_freeze.tres b/assets/models/tiles/tile_area_freeze.tres index 7fc9794..d5d719a 100644 --- a/assets/models/tiles/tile_area_freeze.tres +++ b/assets/models/tiles/tile_area_freeze.tres @@ -1,41 +1,41 @@ -[gd_resource type="ArrayMesh" format=4 uid="uid://dr80txgr61irt"] +[gd_resource type="ArrayMesh" format=4 uid="uid://dqo83gvaay6sn"] [ext_resource type="Texture2D" uid="uid://cp7jtg77hc078" path="res://assets/textures/power_tile/freeze_area_tile.png" id="1_3pijd"] -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2kxvc"] +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ev6sk"] resource_name = "boost" cull_mode = 2 -albedo_color = Color(0.905882, 0.905882, 0.905882, 0.45098) +albedo_color = Color(0.91, 0.91, 0.91, 0.45098) albedo_texture = ExtResource("1_3pijd") -[sub_resource type="ArrayMesh" id="ArrayMesh_ggb2g"] +[sub_resource type="ArrayMesh" id="ArrayMesh_w3aij"] _surfaces = [{ "aabb": AABB(-0.282176, -0.000324821, -0.282176, 0.564351, 0.050792, 0.564351), "format": 34896613377, "index_count": 36, -"index_data": PackedByteArray("AAABAAIAAAADAAEABAABAAMABQADAAAABQAEAAMAAgAFAAAABAAGAAEAAgABAAYABgAEAAUAAgAHAAUABgAFAAcAAgAGAAcA"), +"index_data": PackedByteArray("BwAEAAUABwAGAAQABQACAAMABQAEAAIAAAAEAAYAAAACAAQABQABAAcABQADAAEAAgABAAMAAgAAAAEAAQAGAAcAAQAAAAYA"), "name": "boost", "primitive": 3, "uv_scale": Vector4(0, 0, 0, 0), "vertex_count": 8, -"vertex_data": PackedByteArray("AAAAAAAAAAD//wAA//8AAP//AAAAAAAAAAAAAP//AAAAAP////8AAAAA/v8AAAAA////////AAD///7/AAAAAA==") +"vertex_data": PackedByteArray("AAD/////AAAAAP7/AAAAAP///////wAA///+/wAAAAD//wAA//8AAP//AAAAAAAAAAAAAP//AAAAAAAAAAAAAA==") }] blend_shape_mode = 0 [resource] -resource_name = "tile_tile_coin_002" +resource_name = "tile_star" _surfaces = [{ "aabb": AABB(-0.282176, -0.000324821, -0.282176, 0.564351, 0.050792, 0.564351), -"attribute_data": PackedByteArray("zuS25MX6sfrF+rbkzuSx+szkQOSr9qfkzOSn5Kv2QOSw+yzlSvvl70r7LOWw++XvSvu0+rD7+u9K+/rvsPu0+pHeot5tIVwhkd5cIW0hot47+6/61PrQ6NT6r/o7+9Do"), +"attribute_data": PackedByteArray("sPss5W0hot47+9DosPv6720hXCE7+6/6sPvl75Heot6r9qfksPu0+pHeXCHM5KfkSvvl78X6sfqr9kDkSvu0+sX6tuTM5EDkSvss5c7ksfrU+tDoSvv6787ktuTU+q/6"), "format": 34896613399, "index_count": 36, -"index_data": PackedByteArray("AAABAAIAAAADAAEABAAFAAYABAAHAAUACAAJAAoACAALAAkADAANAA4ADAAPAA0AEAARABIAEAATABEAFAAVABYAFAAXABUA"), -"material": SubResource("StandardMaterial3D_2kxvc"), -"name": "tile_diamond_holo", +"index_data": PackedByteArray("FgANABAAFgATAA0AEQAIAAsAEQAOAAgAAAAMABIAAAAGAAwADwADABUADwAJAAMABwAEAAoABwABAAQABQAUABcABQACABQA"), +"material": SubResource("StandardMaterial3D_ev6sk"), +"name": "tile_area_freeze", "primitive": 3, "uv_scale": Vector4(0, 0, 0, 0), "vertex_count": 24, -"vertex_data": PackedByteArray("AAAAAAAA/z///wAA////P///AAAAAP8/AAAAAP///z///wAAAAD////////////////+/wAA/////wAA/////wAA//////+///8AAP///78AAAAA////v/////////+///8AAAAAAAAAAP7/AAAAAAAAAAAAAAAA///+/wAAAAD/////////vwAA/v8AAP+////+/wAA/78AAP//////vwAA/v8AAKoqAAAAAP//qioAAAAAAACqKgAA/////6oqAAD/fwAA/38AAP9/AAD/f/+//3//v/9//7//f/+//3///////////////////////7//v/+//7//v/+//7//v////3////9/////f////39U1VTVVNVU1VTVVNVU1VTV") +"vertex_data": PackedByteArray("AAD//////78AAP//////vwAA/////6oqAAD+/wAAAAAAAP7/AAD/vwAA/v8AAKoq/////////7//////////v//////////////+/wAAAAD///7/AAD/v////v8AAP////8AAP///7///wAA////P///AAD///////8AAAAAAAD//wAAAAD/P///AAAAAP//AAAAAP///78AAAAA////PwAAAAD//6oqAAAAAAAAAAAAAAAAAAD/PwAAAAAAAKoq/////////39U1VTV/7//v////39U1VTV/////////3//v/9//7//v////3//v/9//////wAA/3//v/9//7//vwAA/3//v/9//////wAA/39U1VTV/7//vwAA/39U1VTV") }] blend_shape_mode = 0 -shadow_mesh = SubResource("ArrayMesh_ggb2g") +shadow_mesh = SubResource("ArrayMesh_w3aij") diff --git a/assets/models/tiles/tile_ghost.tres b/assets/models/tiles/tile_ghost.tres index d1886f9..149e3be 100644 --- a/assets/models/tiles/tile_ghost.tres +++ b/assets/models/tiles/tile_ghost.tres @@ -1,9 +1,12 @@ [gd_resource type="ArrayMesh" format=4 uid="uid://bqvqj3fhf5x51"] +[ext_resource type="Texture2D" uid="uid://dv0datyodktqu" path="res://assets/textures/power_tile/ghost_tile.png" id="1_avhe4"] + [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ev6sk"] resource_name = "boost" cull_mode = 2 albedo_color = Color(0.91, 0.91, 0.91, 0.45098) +albedo_texture = ExtResource("1_avhe4") [sub_resource type="ArrayMesh" id="ArrayMesh_w3aij"] _surfaces = [{ diff --git a/assets/textures/power_tile/ghost_tile.png b/assets/textures/power_tile/ghost_tile.png new file mode 100644 index 0000000..827ed58 Binary files /dev/null and b/assets/textures/power_tile/ghost_tile.png differ diff --git a/assets/textures/power_tile/ghost_tile.png.import b/assets/textures/power_tile/ghost_tile.png.import new file mode 100644 index 0000000..a67314b --- /dev/null +++ b/assets/textures/power_tile/ghost_tile.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dv0datyodktqu" +path.s3tc="res://.godot/imported/ghost_tile.png-472e4da46a96c33de653b9d0ab2de179.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/textures/power_tile/ghost_tile.png" +dest_files=["res://.godot/imported/ghost_tile.png-472e4da46a96c33de653b9d0ab2de179.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/scenes/main.tscn b/scenes/main.tscn index c35314c..1d47465 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -29,6 +29,7 @@ [ext_resource type="Texture2D" uid="uid://cupfmb5m15kmf" path="res://assets/graphics/touch_control/wall.png" id="27_yq6so"] [ext_resource type="Texture2D" uid="uid://3up2su2e0lfa" path="res://assets/graphics/touch_control/freeze_area.png" id="28_fv21b"] [ext_resource type="Texture2D" uid="uid://ckhdyxnho6sjp" path="res://assets/graphics/touch_control/spawn_tile.png" id="28_j8jky"] +[ext_resource type="Texture2D" uid="uid://b2vhatfmufn3d" path="res://assets/graphics/touch_control/ghost.png" id="33_5q0nq"] [ext_resource type="Script" uid="uid://86ikh0wuqk7v" path="res://scripts/ui/powerup_inventory_ui.gd" id="powerup_ui_script"] [ext_resource type="Script" uid="uid://b54tfa0n6kogi" path="res://scripts/managers/touch_controls.gd" id="touch_manager"] [ext_resource type="Script" uid="uid://djiml4sh61dc1" path="res://scripts/ui/virtual_joystick.gd" id="virtual_joystick"] @@ -9881,7 +9882,7 @@ expand_icon = true layout_mode = 2 size_flags_horizontal = 3 focus_mode = 0 -icon = ExtResource("27_dgi5k") +icon = ExtResource("33_5q0nq") flat = true icon_alignment = 1 expand_icon = true diff --git a/scenes/player.gd b/scenes/player.gd index 4bb00e4..9c9c8ba 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -613,11 +613,20 @@ func _apply_tint_recursive(node: Node, color: Color): # If color is WHITE (reset), clear the overlay if color == Color.WHITE: node.material_overlay = null + node.transparency = 0.0 # Reset else: # If color is Blue (frozen), make it semi-transparent overlay mat.albedo_color = color - mat.albedo_color.a = 0.5 # Semi-transparent - mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + + # Special Ghost Effect (Invisible Mode) + if color.a < 1.0: + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + node.transparency = 0.5 # Make base mesh transparent too if possible, depending on material + else: + # Frozen + mat.albedo_color.a = 0.5 # Semi-transparent overlay + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + node.material_overlay = mat for child in node.get_children(): @@ -658,6 +667,37 @@ func apply_stagger(duration: float = 1.5): else: _apply_tint_recursive(self, Color.WHITE) # Remove tint +@rpc("any_peer", "call_local") +func apply_slow_effect(duration: float = 3.0): + # "area with blue like wall... Player who cross on that area will got slowed and freeze effect" + # Visual: Blue Tint + _apply_tint_recursive(self, Color(0.6, 0.8, 1.0)) # Icy Blue + + # Logic: Slow Movement speed + if movement_manager: + # Use 0.2 multipliers to match "slowed" request (20% speed) + movement_manager.set_speed_multiplier(0.2) + + print("Player %s is slowed for %.1f seconds" % [name, duration]) + + # Restore after duration + # Note: If they stand in the zone, this will be re-applied constantly, resetting the visual + await get_tree().create_timer(duration).timeout + + # Only restore if not re-applied recently (simple check: if still tinted?) + # A better way is managing a "slow_timer" but for now let's just reset if timer expires. + # The persistent zone logic reapplies every frame, so we want this timer to be short + # OR we rely on the zone logic. + # The RPC call says "apply_slow_effect(0.5)", so it expires quickly. + + if movement_manager: + movement_manager.set_speed_multiplier(1.0) + + if immunity_timer > 0: + _apply_tint_recursive(self, Color(0.5, 1.0, 0.5)) + else: + _apply_tint_recursive(self, Color.WHITE) + func playerboard_is_empty() -> bool: for item in playerboard: if item != -1: diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index 4a21bb2..ccffa55 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -70,7 +70,11 @@ func simple_move_to(grid_position: Vector2i) -> bool: return false var cell_item = enhanced_gridmap.get_cell_item(Vector3i(grid_position.x, 0, grid_position.y)) - if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items: + + # Allow passing through Walls (Item 4) if Invisible + var is_wall_passable = player.get("is_invisible") and cell_item == 4 + + if (cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items) and not is_wall_passable: return false if player.is_position_occupied(grid_position): @@ -234,7 +238,14 @@ func highlight_movement_range(): # Check basic walkability var cell_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) - if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items or player.is_position_occupied(test_pos): + + # Allow passing through Walls (Item 4) if Invisible + var is_wall_passable = player.get("is_invisible") and cell_item == 4 + + if (cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items) and not is_wall_passable: + continue + + if player.is_position_occupied(test_pos): continue # Check if there's a valid path to this cell @@ -284,8 +295,14 @@ func can_reach_cell(target_pos: Vector2i, blocked_cells: Array) -> bool: # Skip if already visited, blocked, or not valid if visited.has(next_pos) or next_pos in blocked_cells: continue - - if not enhanced_gridmap.is_position_valid(next_pos) or not enhanced_gridmap.is_cell_walkable(next_pos, 0): + + # Custom Walkable Check incorporating invisibility + var cell_item = enhanced_gridmap.get_cell_item(Vector3i(next_pos.x, 0, next_pos.y)) + var is_wall_passable = player.get("is_invisible") and cell_item == 4 + + if (not enhanced_gridmap.is_position_valid(next_pos) or \ + (cell_item in enhanced_gridmap.non_walkable_items and not is_wall_passable) or \ + cell_item == -1): continue if player.is_position_occupied(next_pos) and next_pos != target_pos: diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index eebeec4..07ec1eb 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -52,8 +52,10 @@ const INVISIBLE_DURATION = 6.0 # Active effect tracking var blocked_tiles: Array[Dictionary] = [] # {position: Vector3i, original_item: int, timer: float} var freeze_zones: Array[Dictionary] = [] # {position: Vector2i, timer: float} +var active_freeze_zones: Array = [] # Array of {center, radius, timer} var invisible_timer: float = 0.0 + # INVENTORY SYSTEM # Stores count of each power-up type. Max 1 per type as per user request? # "player can store 1 of each different power up" @@ -189,47 +191,107 @@ func _execute_faster_speed(): NotificationManager.send_message(player, "Speed Boost! (5s)", NotificationManager.MessageType.POWERUP) func _execute_area_freeze(): - # "Area Freeze... slow their speed movement for 3s" - # Reuse freeze logic but simpler duration - var center = player.current_position - # 3x3 around player - var radius = 1 - print("Player %s executing Area Freeze" % player.name) + # "area with blue like wall but with far away from the player who use it" + # "Make it like 4 floor first (offset 4) and the continue to bigger when the level... is close to max" - # Get enemies in radius + # 1. Calculate Forward Direction based on Rotation + # Rotation 0 = South (+Z), PI = North (-Z) + var rot = player.rotation.y + var forward_x = round(sin(rot)) + var forward_z = round(cos(rot)) + var forward_vec = Vector2i(forward_x, forward_z) + + # If rotation is diagonal or imprecise, normalize to cardinal + if abs(forward_x) > abs(forward_z): + forward_vec = Vector2i(sign(forward_x), 0) + else: + forward_vec = Vector2i(0, sign(forward_z)) + + # 2. Offset Center (4 tiles away) + var offset_dist = 4 + var center = player.current_position + (forward_vec * offset_dist) + + # 3. Determine Radius based on Level + # Level 1-4: Radius 1 (3x3 area) + # Level 5-8: Radius 2 (5x5 area) + var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1) + var radius = 1 + if current_lvl >= 5: + radius = 2 # Bigger area at high levels + + print("Player %s executing Area Freeze at %s (Offset %s, Lvl %d, Rad %d)" % [player.name, center, forward_vec, current_lvl, radius]) + + # Register Zone for persistence + active_freeze_zones.append({ + "center": center, + "radius": radius, + "timer": FREEZE_SLOW_DURATION # Same duration as the visual + }) + + # Initial Check (Instant Feedback) var all_players = player.get_tree().get_nodes_in_group("Players") for p in all_players: - if p == player: continue - var dist = Vector2(p.current_position.x - center.x, p.current_position.y - center.y).length() - if dist <= 1.5: # Adjacent or on top + # Check distance (Chebyshev distance for square area) + var dx = abs(p.current_position.x - center.x) + var dy = abs(p.current_position.y - center.y) + + # If inside square radius + if dx <= radius and dy <= radius: p.rpc("apply_slow_effect", FREEZE_SLOW_DURATION) + NotificationManager.send_message(p, "Caught in Freeze Zone!", NotificationManager.MessageType.WARNING) - # Visual Feedback (Icy Floor for 3s?) + # Visual Feedback (Turn Floor Blue - Item 12 on Layer 0) if player.is_multiplayer_authority(): - for x in range(-1, 2): - for y in range(-1, 2): + # Sync Icy Floor (Layer 0) + for x in range(-radius, radius + 1): + for y in range(-radius, radius + 1): var pos = center + Vector2i(x, y) - var main = player.get_tree().get_root().get_node_or_null("Main") - if main: main.rpc("sync_grid_item", pos.x, 2, pos.y, 15) # Icy decal + if enhanced_gridmap.is_position_valid(pos): + var main = player.get_tree().get_root().get_node_or_null("Main") + # Use Item 12 (Blue Freeze Tile) on Layer 0 (Floor) + if main: main.rpc("sync_grid_item", pos.x, 0, pos.y, 12) # Cleanup visual timer (managed locally by author) get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func(): - for x in range(-1, 2): - for y in range(-1, 2): + for x in range(-radius, radius + 1): + for y in range(-radius, radius + 1): var pos = center + Vector2i(x, y) - var main = player.get_tree().get_root().get_node_or_null("Main") - if main: main.rpc("sync_grid_item", pos.x, 2, pos.y, -1) + if enhanced_gridmap.is_position_valid(pos): + var main = player.get_tree().get_root().get_node_or_null("Main") + # Restore to Item 0 (Standard Floor) + if main: main.rpc("sync_grid_item", pos.x, 0, pos.y, 0) ) func _execute_block_floor(target: Node3D): # Existing logic for blocking, reused # "Wall Block" usually means block WHERE YOU ARE or FRONT? # Original code blocked target's floor. - # If target is self, blocks self's floor? Maybe defensive. - # "Have the same cooldown as Faster" -> Just logic reuse. + # User Request: "choose one between horizontal or vertical all the way to the colus or rows" + # We interpret "Choose one" as random 50/50 since there's no UI for sub-selection. + + # Check for Immunity (Invisible Mode) + if target.get("is_invisible"): + NotificationManager.send_message(target, "blocked!", NotificationManager.MessageType.POWERUP) + # We should probably notify the attacker too? + return + var center = target.current_position - var neighbors = enhanced_gridmap.get_neighbors(center, 1) - neighbors.append({"position": center}) + var is_horizontal = rng.randf() < 0.5 + var neighbors = [] + + if is_horizontal: + # Block entire Row (Fixed Z, iterate all X) + # Assuming 'center.y' corresponds to Grid Z-row + var row_z = center.y + for x in range(enhanced_gridmap.columns): + neighbors.append({"position": Vector2i(x, row_z)}) + print("Player %s activated Wall Block: HORIZONTAL ROW (Z=%d)" % [player.name, row_z]) + else: + # Block entire Column (Fixed X, iterate all Z) + var col_x = center.x + for z in range(enhanced_gridmap.rows): + neighbors.append({"position": Vector2i(col_x, z)}) + print("Player %s activated Wall Block: VERTICAL COLUMN (X=%d)" % [player.name, col_x]) for n in neighbors: var pos = n.position @@ -240,8 +302,9 @@ func _execute_block_floor(target: Node3D): if main: main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, 4) - var original_item = 0 # Assume floor - # If we have logic to save original, fine, but for now just 0 + # We don't save original item here properly in this loop if we overwrite something important, + # but for Floor 0 it's usually just ground (0) or obstacles. + # If we overwrite another Wall, it's fine. blocked_tiles.append({ "position": block_pos, "original_item": 0, @@ -250,9 +313,13 @@ func _execute_block_floor(target: Node3D): NotificationManager.send_message(target, "Wall Block Created!", NotificationManager.MessageType.POWERUP) func _execute_invisible_mode(target: Node3D): - # Existing logic kept as ID 14 placeholder target.is_invisible = true invisible_timer = INVISIBLE_DURATION + + # Visual Feedback: Ghost Mode (Low Alpha) + if target.has_method("sync_modulate"): + target.rpc("sync_modulate", Color(1.0, 1.0, 1.0, 0.4)) # 40% Opacity + NotificationManager.send_message(target, "Invisible Mode!", NotificationManager.MessageType.POWERUP) @@ -273,12 +340,12 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true): if rng.randf() > 0.5: continue var item_id: int - # 70% Chance for PowerUp (11-14) + # 70% Chance for Normal Tile (7-10) if rng.randf() < 0.7: - item_id = rng.randi_range(11, 14) - else: - # 30% Chance for Normal Tile (7-10) item_id = rng.randi_range(7, 10) + else: + # 30% Chance for PowerUp (11-14) + item_id = rng.randi_range(11, 14) var cell = Vector3i(pos.x, 1, pos.y) @@ -290,23 +357,35 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true): func _update_freeze_zones(delta: float): # Only the authority of this manager (the caster) handles the timers and cleanup - if not player.is_multiplayer_authority(): - return + if not active_freeze_zones.is_empty(): + var zones_to_remove = [] - var zones_to_remove = [] - for i in range(freeze_zones.size()): - freeze_zones[i].timer -= delta - if freeze_zones[i].timer <= 0: - zones_to_remove.append(i) + for i in range(active_freeze_zones.size()): + var zone = active_freeze_zones[i] + zone.timer -= delta - # Cleanup expired zones - zones_to_remove.reverse() - for idx in zones_to_remove: - var zone = freeze_zones[idx] - var main = player.get_tree().get_root().get_node_or_null("Main") - if main: - main.rpc("sync_grid_item", zone.position.x, 2, zone.position.y, -1) - freeze_zones.remove_at(idx) + # Check for players inside this zone (Trap Logic) + var all_players = player.get_tree().get_nodes_in_group("Players") + for p in all_players: + # Invisible Immunity (Passive) + if p.get("is_invisible"): continue + + var dx = abs(p.current_position.x - zone.center.x) + var dy = abs(p.current_position.y - zone.center.y) + + # If inside zone + if dx <= zone.radius and dy <= zone.radius: + # Apply slow effect repeatedly + # We use a short duration so it expires quickly if they leave + p.rpc("apply_slow_effect", 0.5) + + if zone.timer <= 0: + zones_to_remove.append(i) + + # Cleanup expired zones + zones_to_remove.reverse() + for idx in zones_to_remove: + active_freeze_zones.remove_at(idx) func _check_for_icy_floor(): # Every player checks if they are standing on an icy floor (item 15 on layer 2) @@ -314,6 +393,10 @@ func _check_for_icy_floor(): if not player.is_multiplayer_authority(): return + # Invisible Immunity (Passive) + if player.is_invisible: + return + if not enhanced_gridmap: return @@ -339,7 +422,6 @@ func _process(delta): if powerup_cooldowns[effect] <= 0: powerup_cooldowns[effect] = 0 emit_signal("cooldown_updated", effect, 0, 0) - print("Cooldown finished for %s" % SpecialEffect.keys()[effect]) # Update Active Buffs (Speed) if active_buffs.has(SpecialEffect.FASTER_SPEED): @@ -392,7 +474,11 @@ func _update_invisible_timer(delta: float): invisible_timer = 0 if is_instance_valid(player): player.is_invisible = false - NotificationManager.send_message(player, NotificationManager.MESSAGES.INVISIBILITY_ENDED, NotificationManager.MessageType.NORMAL) + # Reset Visuals + if player.has_method("sync_modulate"): + player.rpc("sync_modulate", Color.WHITE) + + NotificationManager.send_message(player, "Invisibility Ended", NotificationManager.MessageType.NORMAL) # ============================================================================= diff --git a/scripts/ui/powerup_inventory_ui.gd b/scripts/ui/powerup_inventory_ui.gd index 6bea140..7282d44 100644 --- a/scripts/ui/powerup_inventory_ui.gd +++ b/scripts/ui/powerup_inventory_ui.gd @@ -160,7 +160,7 @@ func _on_cooldown_updated(effect: int, time_left: float, max_time: float): var cd_lbl = btn.get_node_or_null("CooldownLabel") if cd_lbl: if time_left > 0: - cd_lbl.text = "%.1f" % time_left + cd_lbl.text = "%d" % int(time_left) btn.disabled = true btn.modulate = Color(0.7, 0.7, 0.7, 0.8) else: