feat: Introduce player input and movement managers to handle continuous, turn-based, and targeted grid interactions.
This commit is contained in:
+38
-12
@@ -34,6 +34,7 @@ func can_rpc() -> bool:
|
|||||||
|
|
||||||
# Special effect states
|
# Special effect states
|
||||||
var is_frozen: bool = false
|
var is_frozen: bool = false
|
||||||
|
var is_stop_frozen: bool = false # Special freeze for Stop n Go phase
|
||||||
var is_invisible: bool = false
|
var is_invisible: bool = false
|
||||||
var original_movement_range: int = 1
|
var original_movement_range: int = 1
|
||||||
|
|
||||||
@@ -718,10 +719,35 @@ func apply_stagger(duration: float = 1.5):
|
|||||||
|
|
||||||
is_frozen = false
|
is_frozen = false
|
||||||
# If still immune, show immunity tint (Green?), otherwise White
|
# If still immune, show immunity tint (Green?), otherwise White
|
||||||
if immunity_timer > 0:
|
# UNLESS we are still stop-frozen (Cyan)
|
||||||
_apply_tint_recursive(self , Color(0.5, 1.0, 0.5)) # Light Green for immunity
|
if is_stop_frozen:
|
||||||
|
_apply_tint_recursive(self, Color.CYAN)
|
||||||
|
elif immunity_timer > 0:
|
||||||
|
_apply_tint_recursive(self, Color(0.5, 1.0, 0.5)) # Light Green for immunity
|
||||||
else:
|
else:
|
||||||
_apply_tint_recursive(self , Color.WHITE) # Remove tint
|
_apply_tint_recursive(self, Color.WHITE) # Remove tint
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func sync_stop_freeze(enabled: bool):
|
||||||
|
# Security: Only allow server (peer 1) or local calls (peer 0)
|
||||||
|
var sender = multiplayer.get_remote_sender_id()
|
||||||
|
if sender != 1 and sender != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_stop_frozen = enabled
|
||||||
|
|
||||||
|
if enabled:
|
||||||
|
_apply_tint_recursive(self, Color.CYAN)
|
||||||
|
print("[STOP n GO] Player %s FROZEN until GO phase" % name)
|
||||||
|
else:
|
||||||
|
# Restore appropriate tint
|
||||||
|
if is_frozen:
|
||||||
|
_apply_tint_recursive(self, Color.BLUE)
|
||||||
|
elif immunity_timer > 0:
|
||||||
|
_apply_tint_recursive(self, Color(0.5, 1.0, 0.5))
|
||||||
|
else:
|
||||||
|
_apply_tint_recursive(self, Color.WHITE)
|
||||||
|
print("[STOP n GO] Player %s UNFROZEN" % name)
|
||||||
|
|
||||||
@rpc("any_peer", "call_local")
|
@rpc("any_peer", "call_local")
|
||||||
func apply_slow_effect(duration: float = 3.0):
|
func apply_slow_effect(duration: float = 3.0):
|
||||||
@@ -837,11 +863,12 @@ func _find_valid_drop_position() -> Vector2i:
|
|||||||
func on_stop_phase_violation():
|
func on_stop_phase_violation():
|
||||||
"""Moving during STOP phase makes you lose and scatter tiles."""
|
"""Moving during STOP phase makes you lose and scatter tiles."""
|
||||||
if not multiplayer.is_server(): return
|
if not multiplayer.is_server(): return
|
||||||
|
if is_stop_frozen: return # Already penalized for this phase
|
||||||
|
|
||||||
print("[STOP n GO] Violation by player %s! Scattering tiles." % name)
|
print("[STOP n GO] Violation by player %s! Scattering tiles." % name)
|
||||||
|
|
||||||
# Stun effect
|
# New Indefinite Freeze until phase ends
|
||||||
apply_stagger(2.0)
|
rpc("sync_stop_freeze", true)
|
||||||
|
|
||||||
# Scatter items
|
# Scatter items
|
||||||
var items_to_scatter = []
|
var items_to_scatter = []
|
||||||
@@ -850,9 +877,6 @@ func on_stop_phase_violation():
|
|||||||
items_to_scatter.append(playerboard[i])
|
items_to_scatter.append(playerboard[i])
|
||||||
playerboard[i] = -1
|
playerboard[i] = -1
|
||||||
|
|
||||||
if items_to_scatter.is_empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
rpc("sync_playerboard", playerboard)
|
rpc("sync_playerboard", playerboard)
|
||||||
|
|
||||||
# Find multiple valid drop positions around the player
|
# Find multiple valid drop positions around the player
|
||||||
@@ -1039,7 +1063,7 @@ func _physics_process(delta):
|
|||||||
func _unhandled_input(event):
|
func _unhandled_input(event):
|
||||||
# Handle power-up usage
|
# Handle power-up usage
|
||||||
if event.is_action_pressed("use_powerup") and is_multiplayer_authority():
|
if event.is_action_pressed("use_powerup") and is_multiplayer_authority():
|
||||||
if is_frozen:
|
if is_frozen or is_stop_frozen:
|
||||||
return
|
return
|
||||||
if powerup_manager and powerup_manager.can_use_special():
|
if powerup_manager and powerup_manager.can_use_special():
|
||||||
powerup_manager.use_special_effect()
|
powerup_manager.use_special_effect()
|
||||||
@@ -1054,6 +1078,8 @@ func _on_slot_gui_input(event, slot_index, slot_ui) -> int:
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
func handle_grid_click(grid_position: Vector2i):
|
func handle_grid_click(grid_position: Vector2i):
|
||||||
|
if is_frozen or is_stop_frozen:
|
||||||
|
return
|
||||||
if input_manager:
|
if input_manager:
|
||||||
input_manager.handle_grid_click(grid_position)
|
input_manager.handle_grid_click(grid_position)
|
||||||
|
|
||||||
@@ -1921,7 +1947,7 @@ func complete_race(final_position: int):
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
func grab_tekton():
|
func grab_tekton():
|
||||||
if not is_multiplayer_authority() or is_carrying_tekton or is_frozen:
|
if not is_multiplayer_authority() or is_carrying_tekton or is_frozen or is_stop_frozen:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Find nearby Tekton
|
# Find nearby Tekton
|
||||||
@@ -1940,7 +1966,7 @@ func sync_grab_tekton(tekton_path: NodePath):
|
|||||||
print("[Player %s] Grabbed Tekton %s" % [name, tekton.name])
|
print("[Player %s] Grabbed Tekton %s" % [name, tekton.name])
|
||||||
|
|
||||||
func throw_tekton():
|
func throw_tekton():
|
||||||
if not is_multiplayer_authority() or not is_carrying_tekton or is_frozen:
|
if not is_multiplayer_authority() or not is_carrying_tekton or is_frozen or is_stop_frozen:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Determine throw direction (where player is facing)
|
# Determine throw direction (where player is facing)
|
||||||
@@ -2084,7 +2110,7 @@ func update_active_player_indicator():
|
|||||||
func knock_tekton():
|
func knock_tekton():
|
||||||
# ... legacy or helper function ...
|
# ... legacy or helper function ...
|
||||||
pass
|
pass
|
||||||
if not is_multiplayer_authority() or is_frozen:
|
if not is_multiplayer_authority() or is_frozen or is_stop_frozen:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Requirement: Full Powerup Bar
|
# Requirement: Full Powerup Bar
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node
|
|||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
# Early return conditions
|
# 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"):
|
if not is_instance_valid(player) or not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots") or player.is_frozen or player.is_stop_frozen:
|
||||||
return
|
return
|
||||||
|
|
||||||
if TurnManager.turn_based_mode:
|
if TurnManager.turn_based_mode:
|
||||||
@@ -83,7 +83,7 @@ func handle_unhandled_input(event):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Turn-based mouse input (handled in unhandled_input)
|
# 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)):
|
if not player.is_multiplayer_authority() or player.is_frozen or player.is_stop_frozen or (TurnManager.turn_based_mode and (not player.is_my_turn or movement_manager.is_moving)):
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Keyboard Shortcuts (Event-based) ---
|
# --- Keyboard Shortcuts (Event-based) ---
|
||||||
@@ -162,7 +162,7 @@ func handle_unhandled_input(event):
|
|||||||
handle_grid_click(click_position)
|
handle_grid_click(click_position)
|
||||||
|
|
||||||
func handle_grid_click(grid_position: Vector2i):
|
func handle_grid_click(grid_position: Vector2i):
|
||||||
if player.is_bot == true or player.is_in_group("Bots"):
|
if player.is_frozen or player.is_stop_frozen or player.is_bot == true or player.is_in_group("Bots"):
|
||||||
return
|
return
|
||||||
var main = player.get_node("/root/Main")
|
var main = player.get_node("/root/Main")
|
||||||
if not main:
|
if not main:
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
|||||||
# print("[Move] Failed: Not authority for ", player.name)
|
# print("[Move] Failed: Not authority for ", player.name)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if player.get("is_frozen"):
|
if player.get("is_frozen") or player.get("is_stop_frozen"):
|
||||||
print("[Move] Failed: Player is frozen")
|
print("[Move] Failed: Player is frozen")
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ func move_to_clicked_position(grid_position: Vector2i) -> bool:
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
# Check if player is frozen
|
# Check if player is frozen
|
||||||
if player.get("is_frozen"):
|
if player.get("is_frozen") or player.get("is_stop_frozen"):
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Validate grid position is within bounds
|
# Validate grid position is within bounds
|
||||||
|
|||||||
@@ -147,6 +147,14 @@ func _start_phase(phase: Phase):
|
|||||||
var phase_name = "GO" if phase == Phase.GO else "STOP"
|
var phase_name = "GO" if phase == Phase.GO else "STOP"
|
||||||
if can_rpc():
|
if can_rpc():
|
||||||
rpc("sync_phase", phase_name, phase_timer)
|
rpc("sync_phase", phase_name, phase_timer)
|
||||||
|
|
||||||
|
# If GO phase starts, clear all STOP phase freezes
|
||||||
|
if phase == Phase.GO:
|
||||||
|
var all_players = get_tree().get_nodes_in_group("Players")
|
||||||
|
for p in all_players:
|
||||||
|
if p.has_method("sync_stop_freeze"):
|
||||||
|
p.rpc("sync_stop_freeze", false)
|
||||||
|
|
||||||
emit_signal("phase_changed", phase_name, phase_timer)
|
emit_signal("phase_changed", phase_name, phase_timer)
|
||||||
|
|
||||||
func can_rpc() -> bool:
|
func can_rpc() -> bool:
|
||||||
@@ -196,8 +204,8 @@ func _apply_arena_setup():
|
|||||||
# Clear existing items on all layers
|
# Clear existing items on all layers
|
||||||
gridmap.clear()
|
gridmap.clear()
|
||||||
|
|
||||||
# Safe Zones Columns: 6, 7, 8 and 14, 15, 16
|
# Safe Zones Columns: 6, 7, 8 (Only one band now)
|
||||||
var safe_columns = [6, 7, 8, 14, 15, 16]
|
var safe_columns = [6, 7, 8]
|
||||||
|
|
||||||
# Create bands based on X (Horizontal Progress)
|
# Create bands based on X (Horizontal Progress)
|
||||||
for x in range(gridmap.columns):
|
for x in range(gridmap.columns):
|
||||||
@@ -263,24 +271,32 @@ func _spawn_mission_tiles():
|
|||||||
gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
||||||
if not gridmap: return
|
if not gridmap: return
|
||||||
|
|
||||||
# Tile IDs for missions: Heart(7), Diamond(8), Star(9), Coin(10)
|
# Forbidden Zones (Start, Safe, Finish) - No items here
|
||||||
var mission_tiles = [7, 8, 9, 10]
|
var forbidden_x = [0, 6, 7, 8, 21]
|
||||||
|
|
||||||
for tile_type in mission_tiles:
|
# Goal items: Heart(7), Diamond(8), Star(9), Coin(10)
|
||||||
var count = 0
|
var goal_items = [7, 8, 9, 10]
|
||||||
while count < 15: # 15 of each type (plenty for finding 3)
|
|
||||||
var x = randi() % gridmap.columns
|
|
||||||
var z = randi() % gridmap.rows
|
|
||||||
|
|
||||||
# Only spawn in walkable areas (Floor 0 must be 0 or 2, and Floor 1 empty)
|
for x in range(gridmap.columns):
|
||||||
var floor_item = gridmap.get_cell_item(Vector3i(x, 0, z))
|
if x in forbidden_x:
|
||||||
if (floor_item == TILE_WALKABLE or floor_item == TILE_SAFE):
|
continue # Clear zone
|
||||||
if gridmap.get_cell_item(Vector3i(x, 1, z)) == -1:
|
|
||||||
gridmap.set_cell_item(Vector3i(x, 1, z), tile_type)
|
for z in range(gridmap.rows):
|
||||||
var main = get_node("/root/Main")
|
# Ensure we don't spawn on obstacles
|
||||||
if main and can_rpc():
|
var base_tile = gridmap.get_cell_item(Vector3i(x, 0, z))
|
||||||
main.rpc("sync_grid_item", x, 1, z, tile_type)
|
if base_tile == TILE_OBSTACLE:
|
||||||
count += 1
|
continue
|
||||||
|
|
||||||
|
# Randomly populate other floors with goal tiles
|
||||||
|
# 30% chance to have a tile to avoid overcrowding
|
||||||
|
if randf() < 0.3:
|
||||||
|
var tile_type = goal_items[randi() % goal_items.size()]
|
||||||
|
gridmap.set_cell_item(Vector3i(x, 1, z), tile_type)
|
||||||
|
|
||||||
|
# Sync to clients
|
||||||
|
var main = get_node("/root/Main")
|
||||||
|
if main:
|
||||||
|
main.rpc("sync_grid_item", x, 1, z, tile_type)
|
||||||
|
|
||||||
func _assign_missions():
|
func _assign_missions():
|
||||||
# NO-OP: Missions are now achievement-based (Complete 3 Goals)
|
# NO-OP: Missions are now achievement-based (Complete 3 Goals)
|
||||||
|
|||||||
Reference in New Issue
Block a user