feat: Introduce PlayerMovementManager to centralize grid-based player and bot movement, including multiplayer synchronization and push interactions.

This commit is contained in:
Yogi Wiguna
2026-03-05 15:32:45 +08:00
parent fb6d9df5db
commit aa26e9f2a4
3 changed files with 93 additions and 13 deletions
+20
View File
@@ -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
+53 -4
View File
@@ -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"
+20 -9
View File
@@ -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