feat: Implement cycle timer toggle, refactor continuous input, and improve movement synchronization.

This commit is contained in:
2026-01-07 05:41:38 +08:00
parent 251b677a2e
commit 6aede0a382
11 changed files with 210 additions and 58 deletions
+3 -2
View File
@@ -323,8 +323,9 @@ func _try_move() -> bool:
_is_processing_action = true
_current_action = "moving"
# Wait for movement tween (approx 0.25s) plus small delay
await get_tree().create_timer(0.3).timeout
# Wait for movement to finish (signal from movement manager)
await actor.movement_manager.movement_finished
if not is_instance_valid(self): return true
_is_processing_action = false
+17
View File
@@ -38,6 +38,13 @@ func _ready():
func initialize(main: Node):
main_scene = main
if LobbyManager:
LobbyManager.enable_cycle_timer_changed.connect(_on_enable_cycle_timer_changed)
func _on_enable_cycle_timer_changed(enabled: bool):
# If disabled mid-cycle, the timer will just freeze in _process
# If enabled mid-cycle, it will resume
pass
func _process(delta):
# Update global match timer if active
@@ -57,6 +64,16 @@ func _process(delta):
# Update cycle timer if cycle is active
if not is_cycle_active:
return
# Skip countdown if timer is disabled
var lobby_manager = get_tree().get_root().get_node_or_null("Main/LobbyManager")
# Note: LobbyManager is an Autoload, so we can access it directly via 'LobbyManager'
if LobbyManager and not LobbyManager.get_enable_cycle_timer():
# If timer is disabled, we just don't decrement.
# But we still keep is_cycle_active = true so the phase is "active" (allowing actions)
# just without the clock ticking down.
return
current_cycle_timer -= delta
+26
View File
@@ -30,6 +30,10 @@ var match_duration: int = 180 # Default 3 minutes
# Randomize spawn locations (configurable in lobby by host)
var randomize_spawn: bool = true # Default enabled
# Timer setting
var enable_cycle_timer: bool = true # Default enabled
signal enable_cycle_timer_changed(enabled: bool)
# Character and area selection
var available_characters: Array[String] = ["Bob", "Gatot", "Masbro", "Oldpop"]
var available_areas: Array[String] = ["Desert", "Forest", "City", "Factory"]
@@ -180,6 +184,25 @@ func sync_randomize_spawn(enabled: bool) -> void:
func get_randomize_spawn() -> bool:
return randomize_spawn
# =============================================================================
# Timer Setting
# =============================================================================
func set_enable_cycle_timer(enabled: bool) -> void:
"""Host sets enable cycle timer. Syncs to all clients."""
enable_cycle_timer = enabled
if is_host:
rpc("sync_enable_cycle_timer", enabled)
@rpc("authority", "call_local", "reliable")
func sync_enable_cycle_timer(enabled: bool) -> void:
"""Sync enable cycle timer from host to clients."""
enable_cycle_timer = enabled
emit_signal("enable_cycle_timer_changed", enabled)
func get_enable_cycle_timer() -> bool:
return enable_cycle_timer
# =============================================================================
# Character Selection
# =============================================================================
@@ -272,6 +295,8 @@ func start_game() -> void:
# Sync match duration to all clients before starting
rpc("sync_match_duration", match_duration)
# Sync timer setting
rpc("sync_enable_cycle_timer", enable_cycle_timer)
# Notify all clients to start
rpc("_on_game_starting")
@@ -394,3 +419,4 @@ func reset() -> void:
match_duration = 180 # Reset to default 3 minutes
selected_area = "Desert"
local_character_index = 0
enable_cycle_timer = true
+44 -35
View File
@@ -9,6 +9,43 @@ func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node
movement_manager = p_movement_manager
race_manager = p_race_manager
func _process(delta):
# Early return conditions
if not is_instance_valid(player) or not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots"):
return
if TurnManager.turn_based_mode:
return
# Continuous movement input
var target_position = player.current_position
if Input.is_action_pressed("move_north"):
target_position += Vector2i(0, -1)
elif Input.is_action_pressed("move_northeast"):
target_position += Vector2i(1, -1)
elif Input.is_action_pressed("move_east"):
target_position += Vector2i(1, 0)
elif Input.is_action_pressed("move_southeast"):
target_position += Vector2i(1, 1)
elif Input.is_action_pressed("move_south"):
target_position += Vector2i(0, 1)
elif Input.is_action_pressed("move_southwest"):
target_position += Vector2i(-1, 1)
elif Input.is_action_pressed("move_west"):
target_position += Vector2i(-1, 0)
elif Input.is_action_pressed("move_northwest"):
target_position += Vector2i(-1, -1)
# Action inputs (still momentary)
if Input.is_action_just_pressed("action_grab"):
player.grab_item(player.current_position)
elif Input.is_action_just_pressed("action_put"):
player.auto_put_item()
if target_position != player.current_position:
movement_manager.simple_move_to(target_position)
func handle_unhandled_input(event):
# Early return if not authorized human player
if not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots"):
@@ -19,41 +56,13 @@ func handle_unhandled_input(event):
if not main:
return
# --- Real-time Keyboard/Touch Input ---
if not TurnManager.turn_based_mode and not movement_manager.is_moving:
var target_position = player.current_position
var input_handled = true
# Turn-based mouse input (handled in unhandled_input)
if not player.is_multiplayer_authority() or (TurnManager.turn_based_mode and (not player.is_my_turn or movement_manager.is_moving)):
return
if Input.is_action_just_pressed("move_north"):
target_position += Vector2i(0, -1)
elif Input.is_action_just_pressed("move_northeast"):
target_position += Vector2i(1, -1)
elif Input.is_action_just_pressed("move_east"):
target_position += Vector2i(1, 0)
elif Input.is_action_just_pressed("move_southeast"):
target_position += Vector2i(1, 1)
elif Input.is_action_just_pressed("move_south"):
target_position += Vector2i(0, 1)
elif Input.is_action_just_pressed("move_southwest"):
target_position += Vector2i(-1, 1)
elif Input.is_action_just_pressed("move_west"):
target_position += Vector2i(-1, 0)
elif Input.is_action_just_pressed("move_northwest"):
target_position += Vector2i(-1, -1)
elif Input.is_action_just_pressed("action_grab"):
player.grab_item(player.current_position)
elif Input.is_action_just_pressed("action_put"):
player.auto_put_item()
else:
input_handled = false
if target_position != player.current_position:
movement_manager.simple_move_to(target_position)
if input_handled:
player.get_viewport().set_input_as_handled()
return
# --- End Real-time Input ---
# --- Real-time Keyboard/Touch Input moved to _process ---
# Handle spawn point selection if not yet selected
# Handle spawn point selection if not yet selected
if not player.spawn_point_selected and player.highlighted_spawn_points.size() > 0:
@@ -65,7 +74,7 @@ func handle_unhandled_input(event):
var click_position = player.raycast_to_grid(from, to)
if click_position in player.highlighted_spawn_points:
if player.select_spawn_point(click_position):
return
return
# Turn-based mouse input
if not player.is_multiplayer_authority() or (TurnManager.turn_based_mode and (not player.is_my_turn or movement_manager.is_moving)):
+33 -2
View File
@@ -20,8 +20,12 @@ func initialize(p_player: Node3D, p_gridmap: Node):
if "use_diagonal_movement" in player:
use_diagonal_movement = player.use_diagonal_movement
signal movement_finished
var buffered_direction: Vector2i = Vector2i.ZERO
var current_move_direction: Vector2i = Vector2i.ZERO
func _process(delta):
if player and not is_moving:
if player:
_handle_rotation(delta)
func _handle_rotation(delta):
@@ -37,7 +41,20 @@ func rotate_towards_target(target_pos: Vector2i):
player.rpc("sync_rotation", target_rotation)
func simple_move_to(grid_position: Vector2i) -> bool:
if not player.is_multiplayer_authority() or is_moving:
if is_moving:
# Calculate direction for buffering
var direction = grid_position - player.current_position
# FIX: Only buffer if direction is DIFFERENT from current move (prevents overshoot)
# We need to know current move direction. We can infer it or store it.
# For now, let's assume if we are moving, we don't buffer same direction.
# But we need to know what the current direction is.
# Let's add a variable `current_move_direction` to the class first.
if direction != current_move_direction:
buffer_move_input(direction)
return false
if not player.is_multiplayer_authority():
return false
# Check if player is frozen
@@ -80,12 +97,26 @@ func simple_move_to(grid_position: Vector2i) -> bool:
var path = [Vector2(player.current_position.x, player.current_position.y), Vector2(grid_position.x, grid_position.y)]
path.pop_front()
current_move_direction = grid_position - player.current_position
# Use the existing RPC to move (assuming player still has this RPC or we move it here)
# For now, we'll call the player's RPC method
player.rpc("start_movement_along_path", path, not (player.is_bot or player.is_in_group("Bots")))
return true
func buffer_move_input(direction: Vector2i):
buffered_direction = direction
func _on_movement_finished():
if buffered_direction != Vector2i.ZERO:
var target = player.current_position + buffered_direction
buffered_direction = Vector2i.ZERO
simple_move_to(target)
else:
current_move_direction = Vector2i.ZERO
emit_signal("movement_finished")
func move_to_clicked_position(grid_position: Vector2i) -> bool:
if not player.is_multiplayer_authority() or is_moving or player.action_points <= 0:
return false
+7 -2
View File
@@ -25,7 +25,9 @@ func _normalize_tile(tile: int) -> int:
# =============================================================================
func grab_item(grid_position: Vector2i) -> bool:
if not enhanced_gridmap or player.action_points <= 0:
var has_ap = player.action_points > 0 if TurnManager.turn_based_mode else true
if not enhanced_gridmap or not has_ap:
return false
var cell = Vector3i(grid_position.x, 1, grid_position.y)
@@ -209,7 +211,10 @@ func bot_try_grab_item() -> bool:
# =============================================================================
func auto_put_item() -> bool:
if not enhanced_gridmap or player.action_points <= 0 or player.is_bot or player.is_in_group("Bots"):
# Check AP only if in turn-based mode
var has_ap = player.action_points > 0 if TurnManager.turn_based_mode else true
if not enhanced_gridmap or not has_ap or player.is_bot or player.is_in_group("Bots"):
return false
# Step 1: Find empty adjacent (or current) grid cells