diff --git a/scripts/managers/lobby_manager.gd b/scripts/managers/lobby_manager.gd index afd7040..01fffd2 100644 --- a/scripts/managers/lobby_manager.gd +++ b/scripts/managers/lobby_manager.gd @@ -62,7 +62,7 @@ signal scarcity_mode_changed(mode: String) var disconnect_reason: String = "" # Stop N Go settings -var sng_go_duration: int = 15 +var sng_go_duration: int = 20 var sng_stop_duration: int = 4 var sng_required_goals: int = 8 diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index 053abcd..169d691 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -35,14 +35,22 @@ signal powerup_unlocked(effect: int, level: int) func get_skill_affected_area(effect: int, center_pos: Vector2i) -> Array[Vector2i]: var area: Array[Vector2i] = [] - match effect: - SpecialEffect.AREA_FREEZE: - # Preview 2 blocks ahead of current hover (if we ever re-enable targeting) - area.append(center_pos) - - SpecialEffect.BLOCK_FLOOR: - # Preview just the single block - area.append(center_pos) + if effect == SpecialEffect.AREA_FREEZE: + var radius = 2 # 5x5 area for freeze + for x in range(-radius, radius + 1): + for z in range(-radius, radius + 1): + area.append(center_pos + Vector2i(x, z)) + elif effect == SpecialEffect.BLOCK_FLOOR: + # Wall logic: project full line based on player orientation + var last_dir = player.movement_manager.last_move_direction if (player and player.movement_manager) else Vector2i(0, 1) + if last_dir.x != 0: + # Vertical Wall + for z in range(enhanced_gridmap.rows): + area.append(Vector2i(center_pos.x, z)) + else: + # Horizontal Wall + for x in range(enhanced_gridmap.columns): + area.append(Vector2i(x, center_pos.y)) return area @@ -97,6 +105,11 @@ var active_freeze_zones: Array = [] # Array of {center, radius, timer} var invisible_timer: float = 0.0 var global_cooldown_timer: float = 0.0 +# Targeting Mode +var is_targeting_mode: bool = false +var targeting_effect: int = -1 +var target_indicator_pos: Vector2i = Vector2i.ZERO + # 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" @@ -225,11 +238,15 @@ func activate_effect(effect: int, target_player: Node3D = null): SpecialEffect.FASTER_SPEED: _execute_faster_speed() SpecialEffect.AREA_FREEZE, SpecialEffect.BLOCK_FLOOR: - # Execute immediately based on direction instead of entering Targeting Mode - if effect == SpecialEffect.BLOCK_FLOOR: - _execute_block_floor() + # TWO-CLICK LOGIC: First click starts Targeting Mode (Projection), second click executes. + if is_targeting_mode and targeting_effect == effect: + # SECOND CLICK: Execute at current indicator position + _execute_targeted_effect_v2(effect, target_indicator_pos) + _exit_targeting_mode() else: - _execute_area_freeze() + # FIRST CLICK: Start targeting + _enter_targeting_mode(effect) + return # Don't apply cooldown or generic effects yet SpecialEffect.INVISIBLE_MODE: _execute_invisible_mode(player) @@ -245,6 +262,61 @@ func activate_effect(effect: int, target_player: Node3D = null): print("[SpecialTiles] Consuming %s after successful activation." % SpecialEffect.keys()[effect]) remove_powerup(effect) +# ============================================================================= +# Targeting Mode Helpers +# ============================================================================= + +func _enter_targeting_mode(effect: int): + is_targeting_mode = true + targeting_effect = effect + print("[SpecialTiles] Entered Targeting Mode for %s" % SpecialEffect.keys()[effect]) + # Visual feedback if needed + +func _exit_targeting_mode(): + if is_targeting_mode: + is_targeting_mode = false + targeting_effect = -1 + if player.action_manager: + player.action_manager.clear_highlights() + print("[SpecialTiles] Exited Targeting Mode.") + +func _update_targeting_preview(): + if not player.is_multiplayer_authority(): return + + # Logic for projection: + # Freeze: 4 tiles ahead + # Wall: Always behind player + var distance = 4 + if targeting_effect == SpecialEffect.BLOCK_FLOOR: + distance = -1 # 1 tile behind + + var move_dir = Vector2i.ZERO + if player.movement_manager: + move_dir = player.movement_manager.current_move_direction + if move_dir == Vector2i.ZERO: + move_dir = player.movement_manager.last_move_direction + + if move_dir == Vector2i.ZERO: + move_dir = Vector2i(0, 1) # Fallback + + var center_pos = player.current_position + (move_dir * distance) + target_indicator_pos = center_pos + + # Show highlights via ActionManager + var area = get_skill_affected_area(targeting_effect, center_pos) + + # User Request: Use yellow/orange like hover (ID 1 in EnhancedGridMap) + var indicator_id = 1 + if player.action_manager: + player.action_manager.highlight_cells_if_authorized(area, indicator_id) + +func _execute_targeted_effect_v2(effect: int, target_pos: Vector2i): + match effect: + SpecialEffect.AREA_FREEZE: + _execute_area_freeze(target_pos) + SpecialEffect.BLOCK_FLOOR: + _execute_block_floor(target_pos) + # ============================================================================= # Check if item is a holo tile @@ -269,38 +341,22 @@ func _execute_faster_speed(): SfxManager.rpc("play_rpc", "speed") NotificationManager.send_message(player, "Speed Boost! (5s)", NotificationManager.MessageType.POWERUP) -func _execute_area_freeze(target_pos: Vector2i = Vector2i.ZERO): +func _execute_area_freeze(target_pos: Vector2i = Vector2i(-9999, -9999)): # VFX: show freeze initiator on all peers if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): player.rpc("play_skill_vfx", "skill_freeze") elif player.has_method("play_skill_vfx"): player.play_skill_vfx("skill_freeze") + + # Determine Position: if passed Sentinel, fallback to projection ahead var center_pos = target_pos + if center_pos.x == -9999: + var last_dir = player.movement_manager.last_move_direction if (player and player.movement_manager) else Vector2i(0, 1) + center_pos = player.current_position + (last_dir * 4) - # Determine Level early for distance calculation + # Determine Level/Radius var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1) - - if center_pos == Vector2i.ZERO: - # Updated: Always 4 tiles ahead as per user request - var distance = 4 - print("[SpecialTiles] Area Freeze logic executing with distance: %d" % distance) - - var movement = player.movement_manager - if movement and movement.current_move_direction != Vector2i.ZERO: - center_pos = player.current_position + movement.current_move_direction * distance - else: - # Fallback if standing still - var last_dir = player.movement_manager.last_move_direction if movement else Vector2i(0, 1) - center_pos = player.current_position + last_dir * distance - - # Allow spawning Area Freeze even out of bounds (user request) - if not enhanced_gridmap.is_position_valid(center_pos): - print("[SpecialTiles] Spawning Area Freeze at out-of-bounds position: ", center_pos) - - # 3. Determine Radius based on Level - var radius = 1 - if current_lvl >= 5: - radius = 2 # Bigger area at high levels + var radius = 2 # Always 5x5 (radius 2) print("Player %s executing Area Freeze at %s (Lvl %d, Rad %d)" % [player.name, center_pos, current_lvl, radius]) @@ -344,39 +400,39 @@ func _execute_area_freeze(target_pos: Vector2i = Vector2i.ZERO): else: NotificationManager.send_message(player, "Hit %d Players!" % hit_count, NotificationManager.MessageType.GOAL) - # Visual Feedback (Overlay Layer 2) + # Visual Feedback (Overlay Layer 1 - Ground Level Overlay) if player.is_multiplayer_authority(): - var main = player.get_tree().get_root().get_node_or_null("Main") - if main and main.has_method("sync_grid_items_batch"): + var main_node = get_node_or_null("/root/Main") + if main_node and main_node.has_method("sync_grid_items_batch"): var batch_data = [] - for x in range(-radius, radius + 1): - for y in range(-radius, radius + 1): - var pos = center_pos + Vector2i(x, y) - if enhanced_gridmap.is_position_valid(pos): - # Use Item 5 (Freeze Floor) on Layer 2 - batch_data.append({"x": pos.x, "y": 2, "z": pos.y, "item": 5}) + for rx in range(-radius, radius + 1): + for rz in range(-radius, radius + 1): + var cell_x = center_pos.x + rx + var cell_z = center_pos.y + rz + # Changed to Y=1 (Floor 1) to match Wall visual logic + batch_data.append({"x": cell_x, "y": 1, "z": cell_z, "item": 5}) if not batch_data.is_empty(): - main.rpc("sync_grid_items_batch", batch_data) + main_node.rpc("sync_grid_items_batch", batch_data) - # Cleanup visual timer (managed locally by author) - get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func(): - var restore_batch = [] - for x in range(-radius, radius + 1): - for y in range(-radius, radius + 1): - var pos = center_pos + Vector2i(x, y) - if enhanced_gridmap.is_position_valid(pos): - # Check if it is STILL Freeze Overlay - var current_check = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 2, pos.y)) - if current_check == 5: - restore_batch.append({"x": pos.x, "y": 2, "z": pos.y, "item": - 1}) - - if not restore_batch.is_empty(): - main.rpc("sync_grid_items_batch", restore_batch) - ) + # Removal timer + get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func(): + var cl_node = get_node_or_null("/root/Main") + if not cl_node: return + var clear_batch = [] + for rx in range(-radius, radius + 1): + for rz in range(-radius, radius + 1): + var cx = center_pos.x + rx + var cz = center_pos.y + rz + # Check if it is STILL Freeze Overlay on Layer 1 + if enhanced_gridmap.get_cell_item(Vector3i(cx, 1, cz)) == 5: + clear_batch.append({"x": cx, "y": 1, "z": cz, "item": -1}) + if not clear_batch.is_empty(): + cl_node.rpc("sync_grid_items_batch", clear_batch) + ) -func _execute_block_floor(target_pos: Vector2i = Vector2i.ZERO): +func _execute_block_floor(target_pos: Vector2i = Vector2i(-999, -999)): # VFX: show wall initiator on all peers if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): player.rpc("play_skill_vfx", "skill_wall") @@ -386,14 +442,13 @@ func _execute_block_floor(target_pos: Vector2i = Vector2i.ZERO): var behind_pos = target_pos var last_dir = player.movement_manager.last_move_direction if player.movement_manager else Vector2i(0, 1) - if behind_pos == Vector2i.ZERO: + if behind_pos == Vector2i(-999, -999): behind_pos = player.current_position - last_dir if not enhanced_gridmap.is_position_valid(behind_pos): return - print("Player %s activated Wall Block behind at %s" % [player.name, behind_pos]) - + # Rollback: Should be vertical or horizontal based on direction var neighbors = [] if last_dir.x != 0: # Moving on X-axis (Columns) -> Vertical Wall (Fixed X, all Z) @@ -405,7 +460,7 @@ func _execute_block_floor(target_pos: Vector2i = Vector2i.ZERO): neighbors.append({"position": Vector2i(x, behind_pos.y)}) if player.is_multiplayer_authority(): - var main = player.get_tree().get_root().get_node_or_null("Main") + var main = get_node_or_null("/root/Main") if main and main.has_method("sync_grid_items_batch"): var batch_data = [] for n in neighbors: @@ -597,6 +652,9 @@ func _process(delta): _update_invisible_timer(delta) _update_freeze_zones(delta) _check_for_icy_floor() + + if is_targeting_mode: + _update_targeting_preview() func _apply_slow_mo(target_player: Node3D): if target_player.has_method("apply_stagger") and target_player.is_frozen: diff --git a/scripts/managers/stop_n_go_manager.gd b/scripts/managers/stop_n_go_manager.gd index c917ce1..fa4f693 100644 --- a/scripts/managers/stop_n_go_manager.gd +++ b/scripts/managers/stop_n_go_manager.gd @@ -29,7 +29,7 @@ const PERMANENT_POWERUP_LOCATIONS: Array[Vector2i] = [ ] var current_phase: Phase = Phase.GO -var phase_timer: float = 15.0 # Initialized dynamically later +var phase_timer: float = 20.0 # Initialized dynamically later var is_active: bool = false var player_missions: Dictionary = {} # player_id -> {target_tile: int, required: int, current: int} @@ -667,16 +667,28 @@ func _scatter_player_tiles(player_node: Node): var peer_id = player_node.name.to_int() var playerboard = player_node.playerboard - var tiles_to_scatter: Array[int] = [] - # Collect all non-empty tiles from playerboard + # Collect all non-empty tile indices from playerboard + var occupied_indices: Array[int] = [] for i in range(playerboard.size()): if playerboard[i] != -1: - tiles_to_scatter.append(playerboard[i]) - playerboard[i] = -1 + occupied_indices.append(i) - if tiles_to_scatter.is_empty(): + if occupied_indices.is_empty(): return # Nothing to scatter + + # Select up to 3 random tiles to scatter + var rng = RandomNumberGenerator.new() + rng.randomize() + occupied_indices.shuffle() + + var tiles_to_scatter: Array[int] = [] + var scatter_count = min(occupied_indices.size(), 3) + + for i in range(scatter_count): + var board_idx = occupied_indices[i] + tiles_to_scatter.append(playerboard[board_idx]) + playerboard[board_idx] = -1 # Remove only the scattered tiles from board # Find valid nearby positions to drop tiles (within radius 3 of player) var center = player_node.current_position @@ -698,7 +710,6 @@ func _scatter_player_tiles(player_node: Node): valid_drop_positions.append(pos) # Scatter tiles onto valid positions - var rng = RandomNumberGenerator.new() rng.randomize() for tile in tiles_to_scatter: