diff --git a/scenes/player.gd b/scenes/player.gd index b92f162..930cf4c 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -723,7 +723,6 @@ func apply_stagger(duration: float = 1.5): if is_multiplayer_authority(): NotificationManager.send_message(self , NotificationManager.MESSAGES.CRUSHED, NotificationManager.MessageType.WARNING) - drop_random_item() # Grant "Smashed" Bonus (1 bar, max 2) if powerup_manager: @@ -1140,15 +1139,16 @@ func handle_grid_click(grid_position: Vector2i): # Modify is_position_occupied to check for selected spawn points func is_position_occupied(pos: Vector2i) -> bool: - for player in get_tree().get_nodes_in_group("Players"): - if player == self: + for p in get_tree().get_nodes_in_group("Players"): + if p == self: continue - if player.spawn_point_selected and player.current_position == pos: + # Check if player has selected a spawn point OR is already visible (active in match) + if (p.spawn_point_selected or p.visible) and p.current_position == pos: return true # Check target position (where they are moving to) - if player.is_player_moving and player.target_position == pos: + if p.is_player_moving and p.target_position == pos: return true return false @@ -1336,12 +1336,16 @@ func simple_move_to(grid_position: Vector2i): func move_player_to_clicked_position(grid_position: Vector2i): movement_manager.move_to_clicked_position(grid_position) -@rpc("any_peer", "call_remote", "unreliable") -func start_movement_along_path(path: Array, clear_visual: bool = true): - if is_player_moving: - return # ALREADY MOVING. Guard against redundant RPCs or interruptions. +@rpc("any_peer", "call_remote", "reliable") +func start_movement_along_path(path: Array, clear_visual: bool = true, force: bool = false): + if is_player_moving and not force: + return # ALREADY MOVING. Guard against redundant RPCs or interruptions unless forced. - print("[Player] %s starting move along path: %s" % [name, path]) + if force and movement_manager: + movement_manager.movement_queue.clear() + movement_manager.current_move_direction = Vector2i.ZERO + + print("[Player] %s starting move along path: %s (Forced: %s)" % [name, path, force]) # SERVER-SIDE VIOLATION CHECK (for Stop n Go) if multiplayer.is_server() and LobbyManager.game_mode == "Stop n Go": @@ -1797,6 +1801,9 @@ func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int # Add new RPC function to notify others about spawn selection @rpc("any_peer", "reliable") func notify_spawn_selected(spawn_pos: Vector2i): + # Mark as selected on all peers so occupancy checks work + spawn_point_selected = true + # Update local highlight state for all clients if spawn_pos in highlighted_spawn_points: highlighted_spawn_points.erase(spawn_pos) diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index b7da430..0ab76ff 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -95,24 +95,16 @@ func handle_unhandled_input(event): if event is InputEventKey and event.pressed and not event.echo: match event.keycode: KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4: - var mode = LobbyManager.get_game_mode() - var is_restricted = GameMode.is_restricted(mode) - if is_restricted: - match event.keycode: - KEY_KP_1, KEY_1: - player.activate_powerup(0) # FASTER_SPEED - KEY_KP_2, KEY_2: - player.activate_powerup(3) # INVISIBLE_MODE (Ghost is now 2) - else: - match event.keycode: - KEY_KP_1, KEY_1: - player.activate_powerup(0) # FASTER_SPEED - KEY_KP_2, KEY_2: - player.activate_powerup(2) # BLOCK_FLOOR - KEY_KP_3, KEY_3: - player.activate_powerup(1) # AREA_FREEZE - KEY_KP_4, KEY_4: - player.activate_powerup(3) # INVISIBLE_MODE + # Unified Mapping for all modes: 1-Speed, 2-Wall, 3-Freeze, 4-Ghost + match event.keycode: + KEY_KP_1, KEY_1: + player.activate_powerup(0) # FASTER_SPEED + KEY_KP_2, KEY_2: + player.activate_powerup(2) # BLOCK_FLOOR + KEY_KP_3, KEY_3: + player.activate_powerup(1) # AREA_FREEZE + KEY_KP_4, KEY_4: + player.activate_powerup(3) # INVISIBLE_MODE # KEY_R: # player.auto_put_item() KEY_Q: diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index 5111293..97c21dd 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -24,6 +24,7 @@ func initialize(p_player: Node3D, p_gridmap: Node): signal movement_finished var movement_queue: Array[Vector2i] = [] # Queue of target grid positions var current_move_direction: Vector2i = Vector2i.ZERO +var last_move_direction: Vector2i = Vector2i(0, 1) # Default forward (towards +Z) func _process(delta): if player: @@ -137,6 +138,8 @@ func simple_move_to(grid_position: Vector2i) -> bool: path.pop_front() current_move_direction = grid_position - player.current_position + if current_move_direction != Vector2i.ZERO: + last_move_direction = current_move_direction if player.is_multiplayer_authority(): # Authority starts their own tween locally @@ -174,54 +177,39 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: NotificationManager.send_message(player, "Cannot Attack in Safe Zone!", NotificationManager.MessageType.WARNING) return false - # 1. Drop Victim's Tiles - if other_player.has_method("drop_all_tiles"): - if _can_rpc(): - other_player.rpc("drop_all_tiles") # Sync drop + # 1. 3-Floor Knockback towards Starting Line (X=0) + var push_direction = Vector2i(-1, 0) # Backwards + var pushed_to_pos = target_pos + var push_path = [] - # 2. Spawn PowerUps around Victim - # We delegate this to the attacker's SpecialTilesManager to handle the spawning authority - if player.special_tiles_manager and player.special_tiles_manager.has_method("spawn_powerups_around"): - player.special_tiles_manager.spawn_powerups_around(other_player.current_position) - - # 3. Knockback / Stagger - # Push them away - var pushed_to_pos = target_pos + direction - - # IMPROVED: Check if destination is valid and walkable to prevent being stuck on 'blocks' - var is_dest_valid = _can_push_to(pushed_to_pos) - - # DEFLECTION LOGIC: If direct path is blocked, try diagonal deflection - if not is_dest_valid: - var alts = [] - if direction.x != 0 and direction.y == 0: # Horizontal push -> try diagonal North/South - alts = [pushed_to_pos + Vector2i(0, 1), pushed_to_pos + Vector2i(0, -1)] - elif direction.y != 0 and direction.x == 0: # Vertical push -> try diagonal East/West - alts = [pushed_to_pos + Vector2i(1, 0), pushed_to_pos + Vector2i(-1, 0)] - elif direction.x != 0 and direction.y != 0: # Diagonal push -> try horizontal/vertical components - alts = [pushed_to_pos - Vector2i(direction.x, 0), pushed_to_pos - Vector2i(0, direction.y)] + # Try to push up to 3 tiles back, building the path as we go + for i in range(3): + var next_back = pushed_to_pos + push_direction + if _can_push_to(next_back): + pushed_to_pos = next_back + push_path.append(Vector2(pushed_to_pos.x, pushed_to_pos.y)) + else: + break # Blocked by wall or edge - for alt in alts: - if _can_push_to(alt): - pushed_to_pos = alt - is_dest_valid = true - break - - if is_dest_valid: - # Valid push - var push_path = [Vector2(pushed_to_pos.x, pushed_to_pos.y)] + if push_path.size() > 0: + # Valid push movement if _can_rpc(): - other_player.rpc("start_movement_along_path", push_path, false) + # Pass 'true' for 'force' parameter to interrupt active movements + other_player.rpc("start_movement_along_path", push_path, false, true) + + # Authority Check: If we are already the authority for the victim (e.g. Host hitting a Bot), + # the 'call_remote' RPC above won't execute locally. We MUST call it manually. + if other_player.is_multiplayer_authority(): + other_player.start_movement_along_path(push_path, false, true) + other_player.target_position = pushed_to_pos # Logical update - # Apply stun/freeze effect as requested (same as wall stagger) - if _can_rpc(): - other_player.rpc("apply_stagger", 1.5) - + # 2. Apply freeze/stun effect (blue tint) + if _can_rpc(): + other_player.rpc("apply_stagger", 1.5) else: - # Wall/Blocked -> Stagger in place (Only if no alternatives found) - if _can_rpc(): - other_player.rpc("apply_stagger", 1.5) + # Handle local execution (e.g. offline or host-only logic) + other_player.apply_stagger(1.5) # 4. Consume Boost (Full) - One hit per charge if player.powerup_manager: @@ -246,12 +234,8 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: else: NotificationManager.send_message(player, "Successful Attack!", NotificationManager.MessageType.GOAL) - # 5. Attack Mode Persistence - # logic moved to consume_boost: checks if <= 0 then disables. - # So we do NOT force disable here. - # player.is_attack_mode = false - - return true + # 5. Block the attacker from moving into the victim's space to prevent overlapping + return false func set_speed_multiplier(multiplier: float): speed_multiplier = multiplier diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index c99cd06..588a512 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -26,7 +26,6 @@ const FREEZE_SLOW_DURATION = 3.0 signal cooldown_updated(effect: int, time_left: float, max_time: float) signal powerup_unlocked(effect: int, level: int) -var wall_orientation_horizontal: bool = false # False = Vertical, True = Horizontal # New Helper functions for Targeting and Preview @@ -35,28 +34,12 @@ func get_skill_affected_area(effect: int, center_pos: Vector2i) -> Array[Vector2 match effect: SpecialEffect.AREA_FREEZE: - var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1) - var radius = 1 - if current_lvl >= 5: - radius = 2 - - for x in range(-radius, radius + 1): - for y in range(-radius, radius + 1): - var pos = center_pos + Vector2i(x, y) - # Validate bounds - if enhanced_gridmap.is_position_valid(pos): - area.append(pos) + # Preview 3 blocks ahead of current hover (if we ever re-enable targeting) + area.append(center_pos) SpecialEffect.BLOCK_FLOOR: - # Logic: Based on toggled orientation state - var is_horizontal = wall_orientation_horizontal - - if is_horizontal: - for x in range(enhanced_gridmap.columns): - area.append(Vector2i(x, center_pos.y)) - else: - for z in range(enhanced_gridmap.rows): - area.append(Vector2i(center_pos.x, z)) + # Preview just the single block + area.append(center_pos) return area @@ -143,12 +126,8 @@ func get_effect_from_item(item_id: int) -> int: match item_id: 11: return SpecialEffect.FASTER_SPEED - 12: - if is_restricted: return -1 - return SpecialEffect.AREA_FREEZE - 13: - if is_restricted: return -1 - return SpecialEffect.BLOCK_FLOOR + 12: return SpecialEffect.AREA_FREEZE + 13: return SpecialEffect.BLOCK_FLOOR 14: return SpecialEffect.INVISIBLE_MODE _: return -1 @@ -231,32 +210,11 @@ func activate_effect(effect: int, target_player: Node3D = null): SpecialEffect.FASTER_SPEED: _execute_faster_speed() SpecialEffect.AREA_FREEZE, SpecialEffect.BLOCK_FLOOR: - # Enter Targeting Mode instead of executing immediately (ONLY for human players) - if not (player.is_bot or player.is_in_group("Bots")): - var main = player.get_tree().get_root().get_node_or_null("Main") - if main and main.ui_manager: - # Toggle Logic for Wall Block - if main.ui_manager.current_action_state == main.ui_manager.ActionState.TARGETING and main.ui_manager.pending_skill_id == effect: - if effect == SpecialEffect.BLOCK_FLOOR: - toggle_wall_orientation() - powerup_cooldowns[effect] = 0.0 # Revert cooldown - emit_signal("cooldown_updated", effect, 0.0, 0.0) - return - - main.ui_manager.current_action_state = main.ui_manager.ActionState.TARGETING - main.ui_manager.pending_skill_id = effect - - var msg = "Select a target area..." - if effect == SpecialEffect.BLOCK_FLOOR: - msg = "Click again to toggle Vertical/Horizontal" - - NotificationManager.send_message(player, msg, NotificationManager.MessageType.NORMAL) - # Do NOT set cooldown yet. Cooldown sets on execution. - # Revert the cooldown set above (hacky but handles the split flow) - powerup_cooldowns[effect] = 0.0 - emit_signal("cooldown_updated", effect, 0.0, 0.0) - print("[SpecialTiles] Entered Targeting Mode for %s" % SpecialEffect.keys()[effect]) - return # Exit, wait for input + # Execute immediately based on direction instead of entering Targeting Mode + if effect == SpecialEffect.BLOCK_FLOOR: + _execute_block_floor() + else: + _execute_area_freeze() SpecialEffect.INVISIBLE_MODE: _execute_invisible_mode(player) @@ -284,13 +242,33 @@ func _execute_faster_speed(): active_buffs[SpecialEffect.FASTER_SPEED] = FASTER_DURATION NotificationManager.send_message(player, "Speed Boost! (5s)", NotificationManager.MessageType.POWERUP) -func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO): +func _execute_area_freeze(target_pos: Vector2i = Vector2i.ZERO): + var center_pos = target_pos + + # Determine Level early for distance calculation + var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1) + if center_pos == Vector2i.ZERO: - # Fallback to old behavior if no target provided (or error) - return + # Calculate distance ahead based on Level + # Gap of 3 floors = 4 tiles ahead + # Gap of 5 floors = 6 tiles ahead + var distance = 4 if current_lvl < 5 else 6 + + 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 + + if not enhanced_gridmap.is_position_valid(center_pos): + # Try a bit closer if out of bounds + center_pos = player.current_position + (center_pos - player.current_position).normalized() * 1.0 + if not enhanced_gridmap.is_position_valid(center_pos): + return # 3. Determine Radius based on Level - var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1) var radius = 1 if current_lvl >= 5: radius = 2 # Bigger area at high levels @@ -366,29 +344,29 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO): main.rpc("sync_grid_items_batch", restore_batch) ) -func toggle_wall_orientation(): - wall_orientation_horizontal = !wall_orientation_horizontal - var mode_str = "HORIZONTAL" if wall_orientation_horizontal else "VERTICAL" - NotificationManager.send_message(player, "Wall Mode: " + mode_str, NotificationManager.MessageType.NORMAL) -func _execute_block_floor(target_pos: Vector2i): - # "Wall Block" - var is_horizontal = wall_orientation_horizontal +func _execute_block_floor(target_pos: Vector2i = Vector2i.ZERO): + # "Wall Block" - Spawn line behind player + 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: + 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]) var neighbors = [] - - if is_horizontal: - # Block entire Row (Fixed Z, iterate all X) - var row_z = target_pos.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 = target_pos.x + if last_dir.x != 0: + # Moving on X-axis (Columns) -> Vertical Wall (Fixed X, all Z) 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]) + neighbors.append({"position": Vector2i(behind_pos.x, z)}) + else: + # Moving on Z-axis (Rows) -> Horizontal Wall (Fixed Z, all X) + for x in range(enhanced_gridmap.columns): + 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") @@ -420,10 +398,7 @@ func _execute_block_floor(target_pos: Vector2i): main.rpc("sync_grid_items_batch", batch_data) # Notify - var all_players = player.get_tree().get_nodes_in_group("Players") - for p in all_players: - if p.current_position == target_pos: - NotificationManager.send_message(p, "Wall Block Created!", NotificationManager.MessageType.POWERUP) + NotificationManager.send_message(player, "Defensive Wall Deployed!", NotificationManager.MessageType.POWERUP) func _execute_invisible_mode(target: Node3D): target.is_invisible = true diff --git a/scripts/ui/powerup_inventory_ui.gd b/scripts/ui/powerup_inventory_ui.gd index 54e7572..34b9c02 100644 --- a/scripts/ui/powerup_inventory_ui.gd +++ b/scripts/ui/powerup_inventory_ui.gd @@ -40,9 +40,9 @@ func _ready(): _setup_btn(2, wall_btn) _setup_btn(3, ghost_btn) - if is_restricted: - if wall_btn: wall_btn.visible = false - if freeze_btn: freeze_btn.visible = false + # All skills available with new logic + if wall_btn: wall_btn.visible = true + if freeze_btn: freeze_btn.visible = true print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size()) @@ -111,21 +111,12 @@ func _setup_btn(effect_id: int, btn: Button): # Determine Label Text based on Effect ID var key_text = "" - var mode = LobbyManager.get_game_mode() - var is_restricted = GameMode.is_restricted(mode) - if is_restricted: - # Restricted Mapping: 1, 2 - match effect_id: - 0: key_text = "1" - 3: key_text = "2" # Ghost is now 2 - _: key_text = "" - else: - # Free Mode Mapping: 1, 2, 3, 4 (Original) - match effect_id: - 0: key_text = "1" - 2: key_text = "2" - 1: key_text = "3" - 3: key_text = "4" + # Consistent mapping: 1, 2, 3, 4 + match effect_id: + 0: key_text = "1" # Speed + 2: key_text = "2" # Wall + 1: key_text = "3" # Freeze + 3: key_text = "4" # Ghost sc_lbl.text = key_text btn.add_child(sc_lbl)