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:
|
else:
|
||||||
sync_modulate(color) # Apply locally if offline/not ready
|
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():
|
func knock_tekton():
|
||||||
if not is_multiplayer_authority() or is_frozen or is_stop_frozen:
|
if not is_multiplayer_authority() or is_frozen or is_stop_frozen:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -282,7 +282,35 @@ func _try_attack_chase() -> bool:
|
|||||||
# No victim found? Just behave normally (grab tiles etc)
|
# No victim found? Just behave normally (grab tiles etc)
|
||||||
return false
|
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(
|
var path = enhanced_gridmap.find_path(
|
||||||
Vector2(actor.current_position),
|
Vector2(actor.current_position),
|
||||||
Vector2(victim.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)
|
# print("[BotController] %s attack blocked by boundary (Not late game yet)." % actor.name)
|
||||||
return false
|
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):
|
if actor.movement_manager.simple_move_to(next_step):
|
||||||
_is_processing_action = true
|
_is_processing_action = true
|
||||||
_current_action = "attacking"
|
_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
|
if not is_instance_valid(self) or not is_instance_valid(actor): return true
|
||||||
_is_processing_action = false
|
_is_processing_action = false
|
||||||
_current_action = "idle"
|
_current_action = "idle"
|
||||||
return true
|
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
|
return false
|
||||||
|
|
||||||
@@ -450,7 +491,15 @@ func _try_move() -> bool:
|
|||||||
var unstuck = await _try_unstuck_move()
|
var unstuck = await _try_unstuck_move()
|
||||||
return unstuck
|
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):
|
if actor.movement_manager.simple_move_to(next_step):
|
||||||
_is_processing_action = true
|
_is_processing_action = true
|
||||||
_current_action = "moving"
|
_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 ===
|
# === NEW LOGIC: Only allow push if in ATTACK MODE ===
|
||||||
if not player.get("is_attack_mode"):
|
if not player.get("is_attack_mode"):
|
||||||
# Standard bumping effect or nothing?
|
# Standard bumping effect (Visual only)
|
||||||
print("[Move] Push blocked: Not in attack mode (%s trying to push)" % player.name)
|
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
|
return false
|
||||||
|
|
||||||
# === SUPER PUSH (Attack Mode) ===
|
# === SUPER PUSH (Attack Mode) ===
|
||||||
print("Player %s SUPER PUSHING %s!" % [player.name, other_player.name])
|
print("Player %s SUPER PUSHING %s!" % [player.name, other_player.name])
|
||||||
|
|
||||||
# SAFE ZONE PROTECTION
|
# Visual Feedback: Attack Bump
|
||||||
# Columns 6, 7, 8 and 14, 15, 16 are Safe Zones
|
if _can_rpc():
|
||||||
var safe_columns = [6, 7, 8, 14, 15, 16]
|
player.rpc("sync_bump", target_pos, false) # Attack bump
|
||||||
if target_pos.x in safe_columns:
|
elif player.has_method("sync_bump"):
|
||||||
print(" - Attack BLOCKED by Safe Zone!")
|
player.sync_bump(target_pos, false)
|
||||||
NotificationManager.send_message(player, "Cannot Attack in Safe Zone!", NotificationManager.MessageType.WARNING)
|
|
||||||
return 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)
|
# 1. 3-Floor Knockback towards Starting Line (X=0)
|
||||||
var push_direction = Vector2i(-1, 0) # Backwards
|
var push_direction = Vector2i(-1, 0) # Backwards
|
||||||
|
|||||||
Reference in New Issue
Block a user