feat: Introduce PlayerMovementManager to centralize grid-based player and bot movement, including multiplayer synchronization and push interactions.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user