diff --git a/scenes/player.gd b/scenes/player.gd index 7250842..506cfbb 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -2261,6 +2261,26 @@ func update_active_player_indicator(): else: sync_modulate(color) # Apply locally if offline/not ready +@rpc("any_peer", "call_local", "unreliable") +func sync_bump(target_pos: Vector2i, is_soft: bool = false): + """Visual attack 'bump' or collision animation.""" + # Always return to LOGICAL position to prevent character drift! + var original_pos = grid_to_world(current_position) + var target_world = grid_to_world(target_pos) + + # If it's a soft bump (non-attack), just a tiny nudge + var strength = 0.4 if not is_soft else 0.15 + var duration = 0.1 if not is_soft else 0.08 + + var mid_pos = original_pos.lerp(target_world, strength) + + var tween = create_tween() + # Ensure the character starts at logical pos if they were drifting + global_position = original_pos + + tween.tween_property(self, "global_position", mid_pos, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + tween.tween_property(self, "global_position", original_pos, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) + func knock_tekton(): if not is_multiplayer_authority() or is_frozen or is_stop_frozen: return diff --git a/scripts/bot_controller.gd b/scripts/bot_controller.gd index 0dd4903..f8835e6 100644 --- a/scripts/bot_controller.gd +++ b/scripts/bot_controller.gd @@ -282,7 +282,35 @@ func _try_attack_chase() -> bool: # No victim found? Just behave normally (grab tiles etc) return false - # Pathfind to victim + # 1. Adjacency Check: If already touching or on same tile, attack directly! + var dist_manhattan = abs(victim.current_position.x - actor.current_position.x) + abs(victim.current_position.y - actor.current_position.y) + if dist_manhattan <= 1: + print("[BotController] %s is close to %s (Dist: %d). Attacking!" % [actor.name, victim.name, dist_manhattan]) + + var push_dir = victim.current_position - actor.current_position + if push_dir == Vector2i.ZERO: + # If overlapping, use actor's last move direction or fallback + push_dir = actor.movement_manager.last_move_direction if actor.movement_manager else Vector2i(1, 0) + + # Trigger push logic directly + var push_success = actor.movement_manager.try_push(victim.current_position, push_dir) + + if not push_success: + # If attack failed (e.g. Safe Zone in Stop n Go), don't just loop! + if LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO): + if victim.current_position.x in [6, 7, 8, 14, 15, 16]: # Safe Zone Columns + print("[BotController] %s target is in Safe Zone. Moving to find better angle." % actor.name) + await _try_unstuck_move() + + _is_processing_action = true + _current_action = "attacking" + await _wait_with_variance(action_delay) + if not is_instance_valid(self) or not is_instance_valid(actor): return true + _is_processing_action = false + _current_action = "idle" + return true + + # 2. Pathfind to victim if not adjacent var path = enhanced_gridmap.find_path( Vector2(actor.current_position), Vector2(victim.current_position), @@ -305,15 +333,28 @@ func _try_attack_chase() -> bool: # print("[BotController] %s attack blocked by boundary (Not late game yet)." % actor.name) return false - # Move to next step (If occupied by victim, movement_manager will trigger PUSH) + # Move to next step + # note: simple_move_to will call try_push if next_step is occupied if actor.movement_manager.simple_move_to(next_step): _is_processing_action = true _current_action = "attacking" - await _wait_with_variance(action_delay) # Shorter delay for attacks? perhaos + await _wait_with_variance(action_delay) if not is_instance_valid(self) or not is_instance_valid(actor): return true _is_processing_action = false _current_action = "idle" return true + else: + # If move failed, it might be because simple_move_to called try_push and returned false + # We check if the target is occupied. If it is, and we are in attack mode, + # we assume an attack attempt was made. + if actor.is_position_occupied(next_step): + _is_processing_action = true + _current_action = "attacking" + await _wait_with_variance(action_delay) + if not is_instance_valid(self) or not is_instance_valid(actor): return true + _is_processing_action = false + _current_action = "idle" + return true return false @@ -450,7 +491,15 @@ func _try_move() -> bool: var unstuck = await _try_unstuck_move() return unstuck - # Execute SINGLE STEP movement using player manager + # 1. Check if next step is blocked by another player/bot + # If we are NOT in attack mode, we should avoid bumping into others if possible + if not actor.get("is_attack_mode") and actor.is_position_occupied(next_step): + # Try to find a detour? For now, just try an unstuck move to get out of the way + print("[BotController] %s path blocked by %s. Detouring." % [actor.name, next_step]) + var unstuck = await _try_unstuck_move() + return unstuck + + # 2. Execute movement if actor.movement_manager.simple_move_to(next_step): _is_processing_action = true _current_action = "moving" diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index 5c51c4c..c48465c 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -168,20 +168,31 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: # === NEW LOGIC: Only allow push if in ATTACK MODE === if not player.get("is_attack_mode"): - # Standard bumping effect or nothing? - print("[Move] Push blocked: Not in attack mode (%s trying to push)" % player.name) + # Standard bumping effect (Visual only) + print("[Move] Push blocked: Not in attack mode (%s trying to push %s)" % [player.name, other_player.name]) + if _can_rpc(): + player.rpc("sync_bump", target_pos, true) # Soft bump + elif player.has_method("sync_bump"): + player.sync_bump(target_pos, true) return false # === SUPER PUSH (Attack Mode) === print("Player %s SUPER PUSHING %s!" % [player.name, other_player.name]) - # SAFE ZONE PROTECTION - # Columns 6, 7, 8 and 14, 15, 16 are Safe Zones - var safe_columns = [6, 7, 8, 14, 15, 16] - if target_pos.x in safe_columns: - print(" - Attack BLOCKED by Safe Zone!") - NotificationManager.send_message(player, "Cannot Attack in Safe Zone!", NotificationManager.MessageType.WARNING) - return false + # Visual Feedback: Attack Bump + if _can_rpc(): + player.rpc("sync_bump", target_pos, false) # Attack bump + elif player.has_method("sync_bump"): + player.sync_bump(target_pos, false) + + # SAFE ZONE PROTECTION (Only in Stop n Go) + if LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO): + # Columns 6, 7, 8 and 14, 15, 16 are Safe Zones + var safe_columns = [6, 7, 8, 14, 15, 16] + if target_pos.x in safe_columns: + print(" - Attack BLOCKED by Safe Zone!") + NotificationManager.send_message(player, "Cannot Attack in Safe Zone!", NotificationManager.MessageType.WARNING) + return false # 1. 3-Floor Knockback towards Starting Line (X=0) var push_direction = Vector2i(-1, 0) # Backwards