diff --git a/scenes/player.gd b/scenes/player.gd index 09d997f..68eedba 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -34,6 +34,7 @@ func can_rpc() -> bool: # Special effect states var is_frozen: bool = false +var is_stop_frozen: bool = false # Special freeze for Stop n Go phase var is_invisible: bool = false var original_movement_range: int = 1 @@ -718,10 +719,35 @@ func apply_stagger(duration: float = 1.5): is_frozen = false # If still immune, show immunity tint (Green?), otherwise White - if immunity_timer > 0: - _apply_tint_recursive(self , Color(0.5, 1.0, 0.5)) # Light Green for immunity + # UNLESS we are still stop-frozen (Cyan) + 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: - _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") func apply_slow_effect(duration: float = 3.0): @@ -837,11 +863,12 @@ func _find_valid_drop_position() -> Vector2i: func on_stop_phase_violation(): """Moving during STOP phase makes you lose and scatter tiles.""" 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) - # Stun effect - apply_stagger(2.0) + # New Indefinite Freeze until phase ends + rpc("sync_stop_freeze", true) # Scatter items var items_to_scatter = [] @@ -850,9 +877,6 @@ func on_stop_phase_violation(): items_to_scatter.append(playerboard[i]) playerboard[i] = -1 - if items_to_scatter.is_empty(): - return - rpc("sync_playerboard", playerboard) # Find multiple valid drop positions around the player @@ -1039,7 +1063,7 @@ func _physics_process(delta): func _unhandled_input(event): # Handle power-up usage if event.is_action_pressed("use_powerup") and is_multiplayer_authority(): - if is_frozen: + if is_frozen or is_stop_frozen: return if powerup_manager and powerup_manager.can_use_special(): powerup_manager.use_special_effect() @@ -1054,6 +1078,8 @@ func _on_slot_gui_input(event, slot_index, slot_ui) -> int: return -1 func handle_grid_click(grid_position: Vector2i): + if is_frozen or is_stop_frozen: + return if input_manager: input_manager.handle_grid_click(grid_position) @@ -1921,7 +1947,7 @@ func complete_race(final_position: int): # ============================================================================= 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 # Find nearby Tekton @@ -1940,7 +1966,7 @@ func sync_grab_tekton(tekton_path: NodePath): print("[Player %s] Grabbed Tekton %s" % [name, tekton.name]) 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 # Determine throw direction (where player is facing) @@ -2084,7 +2110,7 @@ func update_active_player_indicator(): func knock_tekton(): # ... legacy or helper function ... pass - if not is_multiplayer_authority() or is_frozen: + if not is_multiplayer_authority() or is_frozen or is_stop_frozen: return # Requirement: Full Powerup Bar diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index 93eef98..2b4497f 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -11,7 +11,7 @@ func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node 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"): + 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 if TurnManager.turn_based_mode: @@ -83,7 +83,7 @@ func handle_unhandled_input(event): return # 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 # --- Keyboard Shortcuts (Event-based) --- @@ -162,7 +162,7 @@ func handle_unhandled_input(event): handle_grid_click(click_position) 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 var main = player.get_node("/root/Main") if not main: diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index 7a729f7..9eb74f5 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -58,7 +58,7 @@ func simple_move_to(grid_position: Vector2i) -> bool: # print("[Move] Failed: Not authority for ", player.name) return false - if player.get("is_frozen"): + if player.get("is_frozen") or player.get("is_stop_frozen"): print("[Move] Failed: Player is frozen") return false @@ -242,7 +242,7 @@ func move_to_clicked_position(grid_position: Vector2i) -> bool: return false # Check if player is frozen - if player.get("is_frozen"): + if player.get("is_frozen") or player.get("is_stop_frozen"): return false # Validate grid position is within bounds diff --git a/scripts/managers/stop_n_go_manager.gd b/scripts/managers/stop_n_go_manager.gd index b78ad98..934a204 100644 --- a/scripts/managers/stop_n_go_manager.gd +++ b/scripts/managers/stop_n_go_manager.gd @@ -147,6 +147,14 @@ func _start_phase(phase: Phase): var phase_name = "GO" if phase == Phase.GO else "STOP" if can_rpc(): 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) func can_rpc() -> bool: @@ -196,8 +204,8 @@ func _apply_arena_setup(): # Clear existing items on all layers gridmap.clear() - # Safe Zones Columns: 6, 7, 8 and 14, 15, 16 - var safe_columns = [6, 7, 8, 14, 15, 16] + # Safe Zones Columns: 6, 7, 8 (Only one band now) + var safe_columns = [6, 7, 8] # Create bands based on X (Horizontal Progress) for x in range(gridmap.columns): @@ -263,24 +271,32 @@ func _spawn_mission_tiles(): gridmap = get_node_or_null("/root/Main/EnhancedGridMap") if not gridmap: return - # Tile IDs for missions: Heart(7), Diamond(8), Star(9), Coin(10) - var mission_tiles = [7, 8, 9, 10] + # Forbidden Zones (Start, Safe, Finish) - No items here + var forbidden_x = [0, 6, 7, 8, 21] - for tile_type in mission_tiles: - var count = 0 - while count < 15: # 15 of each type (plenty for finding 3) - var x = randi() % gridmap.columns - var z = randi() % gridmap.rows + # Goal items: Heart(7), Diamond(8), Star(9), Coin(10) + var goal_items = [7, 8, 9, 10] + + for x in range(gridmap.columns): + if x in forbidden_x: + continue # Clear zone - # Only spawn in walkable areas (Floor 0 must be 0 or 2, and Floor 1 empty) - var floor_item = gridmap.get_cell_item(Vector3i(x, 0, z)) - if (floor_item == TILE_WALKABLE or floor_item == TILE_SAFE): - if gridmap.get_cell_item(Vector3i(x, 1, z)) == -1: - gridmap.set_cell_item(Vector3i(x, 1, z), tile_type) - var main = get_node("/root/Main") - if main and can_rpc(): - main.rpc("sync_grid_item", x, 1, z, tile_type) - count += 1 + for z in range(gridmap.rows): + # Ensure we don't spawn on obstacles + var base_tile = gridmap.get_cell_item(Vector3i(x, 0, z)) + if base_tile == TILE_OBSTACLE: + 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(): # NO-OP: Missions are now achievement-based (Complete 3 Goals)