extends Node # PowerUpManager - Handles Boost Meter (Time-based logic) # Note: Inventory logic is now in SpecialTilesManager + PlayerboardManager const MAX_BOOST: float = 100.0 # Fills to 100. Activation requires FULL bar (>= 100). const BASE_FILL_RATE: float = 10.0 # 10 points per second (10s to full) - user requested faster/simpler var player: Node3D var enhanced_gridmap: Node var goal_manager: Node # Progressive Difficulty Settings const FILL_TIMES: Array = [2.0, 3.0, 4.0, 5.0, 6.0, 7.5, 8.5, 10.0] # Seconds to full for Lvl 1-8 (2s to 10s) var current_level: int = 1 # Boost State var current_boost: float = 0.0 # Alias for compatibility with BotStrategicPlanner var current_points: float: get: return current_boost set(value): current_boost = value # Also alias MAX_POINTS const MAX_POINTS = MAX_BOOST signal points_changed(current: int, max_points: int) # Reused for UI (int casting) signal bar_filled() signal boost_reset() func initialize(p_player: Node3D, p_gridmap: Node): player = p_player enhanced_gridmap = p_gridmap # Find GoalManager var main = player.get_tree().get_root().get_node_or_null("Main") if main: goal_manager = main.get_node_or_null("GoalManager") set_process(true) func _process(delta): if not multiplayer.has_multiplayer_peer(): return if not is_instance_valid(player) or not player.is_multiplayer_authority(): return # Only fill if not full if current_boost < MAX_BOOST: var multiplier = 1.0 if goal_manager: # Use authority ID for lookup multiplier = goal_manager.get_boost_multiplier(player.get_multiplayer_authority()) # Calculate Dynamic Fill Rate based on Level # Level 1 (Index 0) -> Level 8 (Index 7) var level_idx = clamp(current_level - 1, 0, FILL_TIMES.size() - 1) var fill_time = FILL_TIMES[level_idx] var current_rate = MAX_BOOST / fill_time # e.g. 100/10 = 10/s, 100/60 = 1.66/s current_boost += current_rate * multiplier * delta current_boost = min(current_boost, MAX_BOOST) # Update UI (Cast to int for compatibility with existing UI slider/bar) emit_signal("points_changed", int(current_boost), int(MAX_BOOST)) if current_boost >= MAX_BOOST: _on_boost_full() func _on_boost_full(): # player.is_attack_mode = true # Removed auto-activate emit_signal("bar_filled") NotificationManager.send_message(player, NotificationManager.MESSAGES.ATTACK_MODE_READY, NotificationManager.MessageType.POWERUP) print("[PowerUp] Player %s Boost Full! Ready for Attack Mode." % player.name) if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): rpc("sync_boost", current_boost) func reset_boost(): current_boost = 0.0 # player.is_attack_mode = false # Do not auto-disable here, managed by usage emit_signal("points_changed", 0, int(MAX_BOOST)) emit_signal("boost_reset") if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): rpc("sync_boost", 0.0) # ============================================================================= # Sync # ============================================================================= @rpc("any_peer", "call_local", "reliable") func sync_boost(value: float): current_boost = value emit_signal("points_changed", int(current_boost), int(MAX_BOOST)) # Client-side Attack Mode visual check (?) if current_boost >= MAX_BOOST: # Could trigger visual effect here pass @rpc("authority", "call_local", "reliable") func sync_boost_level(level: int): current_level = level var level_idx = clamp(current_level - 1, 0, FILL_TIMES.size() - 1) print("[PowerUp] Difficulty synced: Level %d (Fill Time: %.1fs)" % [current_level, FILL_TIMES[level_idx]]) # ============================================================================= # Getters # ============================================================================= func get_points() -> int: return int(current_boost) func get_max_points() -> int: return int(MAX_BOOST) func get_fill_percentage() -> float: return current_boost / MAX_BOOST func get_time_until_full() -> float: if current_boost >= MAX_BOOST: return 0.0 # Calculate remaining points needed var remaining = MAX_BOOST - current_boost # Get current fill rate var level_idx = clamp(current_level - 1, 0, FILL_TIMES.size() - 1) var fill_time = FILL_TIMES[level_idx] var current_rate = MAX_BOOST / fill_time if current_rate <= 0: return 0.0 return remaining / current_rate func get_bars() -> int: """Returns the number of filled segments (0-4).""" # Each bar is 25 points (100 / 4) return int(current_boost / 25.0) func can_use_special() -> bool: # Use small epsilon for float comparison to avoid "99.999" issues return current_boost >= (MAX_BOOST - 0.1) func enable_attack_mode(): """Enable Attack Mode without consuming boost.""" player.set("is_attack_mode", true) NotificationManager.send_message(player, NotificationManager.MESSAGES.ATTACK_MODE_READY, NotificationManager.MessageType.POWERUP) print("[PowerUp] Attack Mode Enabled (Free).") func use_special_effect() -> bool: if not can_use_special(): return false # Restriction: Cannot use attack mode while carrying a Tekton or in Ghost mode if player.is_carrying_tekton: NotificationManager.send_message(player, "Cannot use Charged Strike while carrying a Tekton!", NotificationManager.MessageType.WARNING) return false if player.get("is_invisible"): NotificationManager.send_message(player, "Cannot use Charged Strike while in Ghost mode!", NotificationManager.MessageType.WARNING) return false if player.has_method("enter_charged_strike"): player.enter_charged_strike() return true return false func consume_boost(amount: float): """Consume a specific amount of boost.""" current_boost -= amount if current_boost <= 0: current_boost = 0.0 player.set("is_attack_mode", false) # Out of fuel emit_signal("boost_reset") # UI update for empty print("[PowerUp] Boost depleted. Attack Mode OFF.") emit_signal("points_changed", int(current_boost), int(MAX_BOOST)) if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): rpc("sync_boost", current_boost) func spawn_boost_reward() -> bool: """Alternative Boost Usage: Spawn a PowerUp Tile properly. Now requires carrying a Tekton (E shortcut).""" if not player.is_carrying_tekton: return false if player.special_tiles_manager and player.special_tiles_manager.has_method("spawn_powerups_around"): # Trigger the synced floor spawn animation if player.has_method("rpc"): player.rpc("sync_floor_spawn_animation") elif player.has_method("play_floor_spawn"): player.play_floor_spawn() # Spawn tiles (Stop N Go gets only common, Free Mode gets 60/40 ratio) player.special_tiles_manager.spawn_powerups_around(player.current_position, true, false, true) # Drop the Tekton after spawning if player.has_method("drop_tekton"): player.drop_tekton() reset_boost() print("[PowerUp] %s used Tekton to SPAWN TILES and consumed Boost." % player.name) return true return false func acquire_smash_bonus(): current_boost += 25.0 # Add 25% boost current_boost = min(current_boost, MAX_BOOST) emit_signal("points_changed", int(current_boost), int(MAX_BOOST)) if current_boost >= MAX_BOOST: _on_boost_full() func add_goal_completion_reward(): """Called when player completes a goal board. Increases difficulty level.""" current_level += 1 if current_level > 8: current_level = 8 var level_idx = clamp(current_level - 1, 0, FILL_TIMES.size() - 1) print("[PowerUp] Player %s Completed Goal. Boost Level Up! Now: %d (Fill Time: %.1fs)" % [player.name, current_level, FILL_TIMES[level_idx]]) # Optional: Notify user of difficulty increase? if multiplayer.is_server() and player.has_method("can_rpc") and player.can_rpc(): rpc("sync_boost_level", current_level)