feat: Establish initial game structure including lobby, UI, and core gameplay managers.
This commit is contained in:
@@ -130,6 +130,13 @@ func _run_ai_tick():
|
||||
print("[BotController] Action Taken: Put (Priority)")
|
||||
return
|
||||
|
||||
# Priority 0: Attack Mode (Aggressive Chase)
|
||||
if actor.get("is_attack_mode"):
|
||||
print("[BotController] Attack Mode Active! Hunting targets...")
|
||||
if await _try_attack_chase():
|
||||
print("[BotController] Action Taken: Attack Pursuit")
|
||||
return
|
||||
|
||||
# Priority 1: Use power-up sabotage if conditions are met
|
||||
if await _try_use_powerup():
|
||||
print("[BotController] Action Taken: PowerUp")
|
||||
@@ -175,6 +182,10 @@ func _try_use_powerup() -> bool:
|
||||
var powerup_manager = actor.get_node_or_null("PowerUpManager")
|
||||
if not powerup_manager or not powerup_manager.can_use_special():
|
||||
return false
|
||||
|
||||
# PREVENT LOOP: If already in Attack Mode, do not try to use it again
|
||||
if actor.get("is_attack_mode"):
|
||||
return false
|
||||
|
||||
# Evaluate sabotage opportunity
|
||||
var eval = strategic_planner.evaluate_sabotage_opportunity()
|
||||
@@ -185,7 +196,21 @@ func _try_use_powerup() -> bool:
|
||||
_is_processing_action = true
|
||||
_current_action = "sabotaging"
|
||||
|
||||
var success = powerup_manager.use_special_effect()
|
||||
# 50/50 Chance: Attack Mode vs Spawn Item
|
||||
var success = false
|
||||
if rng.randf() > 0.5:
|
||||
# Attack Mode
|
||||
success = powerup_manager.use_special_effect()
|
||||
print("[BotController] %s chose ATTACK MODE." % actor.name)
|
||||
else:
|
||||
# Spawn Item (if supported)
|
||||
if powerup_manager.has_method("spawn_boost_reward"):
|
||||
success = powerup_manager.spawn_boost_reward()
|
||||
print("[BotController] %s chose SPAWN ITEM." % actor.name)
|
||||
else:
|
||||
# Fallback to attack mode if method missing
|
||||
success = powerup_manager.use_special_effect()
|
||||
|
||||
if success:
|
||||
print("[BotController] %s used power-up (reason: %s)" % [actor.name, eval.reason])
|
||||
NotificationManager.send_message(actor, NotificationManager.MESSAGES.USED_SPECIAL_POWER, NotificationManager.MessageType.POWERUP)
|
||||
@@ -197,6 +222,58 @@ func _try_use_powerup() -> bool:
|
||||
_current_action = "idle"
|
||||
return success
|
||||
|
||||
# =============================================================================
|
||||
# Attack Mode Logic (Aggressive Chase)
|
||||
# =============================================================================
|
||||
|
||||
func _try_attack_chase() -> bool:
|
||||
"""Find nearest player and move towards them to RAM them."""
|
||||
var victim = _find_nearest_victim()
|
||||
if not victim:
|
||||
# No victim found? Just behave normally (grab tiles etc)
|
||||
return false
|
||||
|
||||
# Pathfind to victim
|
||||
var path = enhanced_gridmap.find_path(
|
||||
Vector2(actor.current_position),
|
||||
Vector2(victim.current_position),
|
||||
0,
|
||||
false,
|
||||
false
|
||||
)
|
||||
|
||||
if path.size() >= 2:
|
||||
var next_step = Vector2i(path[1].x, path[1].y)
|
||||
|
||||
# Move to next step (If occupied by victim, movement_manager will trigger PUSH)
|
||||
if actor.movement_manager.simple_move_to(next_step):
|
||||
_is_processing_action = true
|
||||
_current_action = "attacking"
|
||||
await _wait_with_variance(action_delay) # Shorter delay for attacks? perhaos
|
||||
if not is_instance_valid(self): return true
|
||||
_is_processing_action = false
|
||||
_current_action = "idle"
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
func _find_nearest_victim() -> Node3D:
|
||||
var best_dist = 9999.0
|
||||
var best_victim = null
|
||||
|
||||
var players = get_tree().get_nodes_in_group("Players")
|
||||
for p in players:
|
||||
if p == actor or p.is_in_group("Spectators"):
|
||||
continue
|
||||
|
||||
# Dist check
|
||||
var dist = abs(p.current_position.x - actor.current_position.x) + abs(p.current_position.y - actor.current_position.y)
|
||||
if dist < best_dist:
|
||||
best_dist = dist
|
||||
best_victim = p
|
||||
|
||||
return best_victim
|
||||
|
||||
# =============================================================================
|
||||
# Grab Tiles
|
||||
# =============================================================================
|
||||
@@ -550,8 +627,8 @@ func _handle_goal_completion():
|
||||
goals_cycle_manager.on_goal_completed(actor, time_remaining)
|
||||
|
||||
var powerup_manager = actor.get_node_or_null("PowerUpManager")
|
||||
if powerup_manager:
|
||||
powerup_manager.add_goal_completion_reward()
|
||||
# if powerup_manager:
|
||||
# powerup_manager.add_goal_completion_reward()
|
||||
|
||||
|
||||
print("[BotController] %s COMPLETED GOAL!" % actor.name)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://c2n4v6brcjtwn
|
||||
@@ -133,12 +133,15 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
|
||||
# Wall/Blocked -> Stagger in place
|
||||
other_player.rpc("apply_stagger", 1.5)
|
||||
|
||||
# 4. Consume Boost
|
||||
# 4. Consume Boost (Full) - One hit per charge
|
||||
if player.powerup_manager:
|
||||
player.powerup_manager.reset_boost()
|
||||
# Consume all available boost to force a full recharge cycle
|
||||
player.powerup_manager.consume_boost(100.0)
|
||||
|
||||
# 5. Reset Attack Mode
|
||||
player.is_attack_mode = false
|
||||
# 5. Attack Mode Persistence
|
||||
# logic moved to consume_boost: checks if <= 0 then disables.
|
||||
# So we do NOT force disable here.
|
||||
# player.is_attack_mode = false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
@@ -683,7 +683,7 @@ func _check_goal_completion():
|
||||
if player.race_manager.check_pattern_match():
|
||||
print("[PlayerboardManager] Goal completed for player %s!" % player.name)
|
||||
|
||||
# Award power-up bar for goal completion
|
||||
# Level up boost difficulty on goal completion
|
||||
var powerup_manager = player.get_node_or_null("PowerUpManager")
|
||||
if powerup_manager:
|
||||
powerup_manager.add_goal_completion_reward()
|
||||
|
||||
@@ -3,13 +3,17 @@ extends Node
|
||||
# PowerUpManager - Handles Boost Meter (Time-based logic)
|
||||
# Note: Inventory logic is now in SpecialTilesManager + PlayerboardManager
|
||||
|
||||
const MAX_BOOST: float = 100.0
|
||||
const BASE_FILL_RATE: float = 4.0 # 4 points per second baseline (25s to full)
|
||||
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
|
||||
|
||||
@@ -49,7 +53,13 @@ func _process(delta):
|
||||
# Use authority ID for lookup
|
||||
multiplier = goal_manager.get_boost_multiplier(player.get_multiplayer_authority())
|
||||
|
||||
current_boost += BASE_FILL_RATE * multiplier * delta
|
||||
# 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)
|
||||
@@ -59,7 +69,7 @@ func _process(delta):
|
||||
_on_boost_full()
|
||||
|
||||
func _on_boost_full():
|
||||
player.is_attack_mode = true
|
||||
# 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! Entering Attack Mode." % player.name)
|
||||
@@ -69,7 +79,7 @@ func _on_boost_full():
|
||||
|
||||
func reset_boost():
|
||||
current_boost = 0.0
|
||||
player.is_attack_mode = false
|
||||
# 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")
|
||||
|
||||
@@ -104,15 +114,51 @@ func get_fill_percentage() -> float:
|
||||
return current_boost / MAX_BOOST
|
||||
|
||||
func can_use_special() -> bool:
|
||||
return current_boost >= MAX_BOOST
|
||||
# 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.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
|
||||
|
||||
# Consume boost
|
||||
reset_boost()
|
||||
# Enable Attack Mode explicitly
|
||||
player.is_attack_mode = true
|
||||
# Do NOT consume boost here. Boost acts as "fuel" for the attacks.
|
||||
# Notification check handled by caller or signal
|
||||
return true
|
||||
|
||||
func consume_boost(amount: float):
|
||||
"""Consume a specific amount of boost."""
|
||||
current_boost -= amount
|
||||
if current_boost <= 0:
|
||||
current_boost = 0.0
|
||||
player.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():
|
||||
rpc("sync_boost", current_boost)
|
||||
|
||||
func spawn_boost_reward() -> bool:
|
||||
"""Alternative Boost Usage: Spawn a PowerUp Tile properly."""
|
||||
if not can_use_special():
|
||||
return false
|
||||
|
||||
if player.special_tiles_manager and player.special_tiles_manager.has_method("spawn_powerups_around"):
|
||||
player.special_tiles_manager.spawn_powerups_around(player.current_position)
|
||||
reset_boost() # Consumes full bar
|
||||
print("[PowerUp] %s used Boost to SPAWN ITEM." % player.name)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
func acquire_smash_bonus():
|
||||
current_boost += 25.0 # Add 25% boost
|
||||
@@ -122,8 +168,12 @@ func acquire_smash_bonus():
|
||||
_on_boost_full()
|
||||
|
||||
func add_goal_completion_reward():
|
||||
current_boost += 50.0 # Reward for completing goal
|
||||
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()
|
||||
"""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?
|
||||
|
||||
@@ -11,6 +11,7 @@ var virtual_joystick: Control
|
||||
var grab_button: Button
|
||||
var put_button: Button
|
||||
var special_button: Button
|
||||
var spawn_boost_button: Button
|
||||
var settings_button: Button
|
||||
|
||||
# Settings - persisted to config file
|
||||
@@ -23,7 +24,8 @@ var joystick_position: Vector2 = Vector2(120, -120) # Relative to bottom-left
|
||||
var button_positions: Dictionary = {
|
||||
"grab": Vector2(-200, -240), # Relative to bottom-right
|
||||
"put": Vector2(-120, -160),
|
||||
"special": Vector2(-200, -80)
|
||||
"special": Vector2(-200, -80),
|
||||
"spawn_boost": Vector2(-120, -80)
|
||||
}
|
||||
var button_scale: float = 1.0
|
||||
|
||||
@@ -93,6 +95,7 @@ func _create_touch_ui():
|
||||
grab_button = _find_or_create_action_button(container, "Grab", "👋", button_positions.grab)
|
||||
put_button = _find_or_create_action_button(container, "Put", "📦", button_positions.put)
|
||||
special_button = _find_or_create_action_button(container, "Special", "⚡", button_positions.special)
|
||||
spawn_boost_button = _find_or_create_action_button(container, "SpawnBoost", "🚀", button_positions.spawn_boost)
|
||||
|
||||
# Create settings button (top-right corner)
|
||||
settings_button = container.get_node_or_null("SettingsBtn")
|
||||
@@ -203,6 +206,7 @@ func _on_button_pressed(button_name: String):
|
||||
"Grab": btn = grab_button
|
||||
"Put": btn = put_button
|
||||
"Special": btn = special_button
|
||||
"SpawnBoost": btn = spawn_boost_button
|
||||
|
||||
if btn:
|
||||
var tween = create_tween()
|
||||
@@ -221,8 +225,32 @@ func _on_button_pressed(button_name: String):
|
||||
"Special":
|
||||
emit_signal("special_pressed")
|
||||
var powerup_mgr = local_player.get_node_or_null("PowerUpManager")
|
||||
if powerup_mgr and powerup_mgr.has_method("use_special_effect"):
|
||||
powerup_mgr.use_special_effect()
|
||||
if powerup_mgr:
|
||||
# Require Full Boost to Activate (User Request: "Connect to boost bar")
|
||||
var can_use = powerup_mgr.can_use_special()
|
||||
var boost_val = powerup_mgr.current_boost
|
||||
print("[TouchControls] Special Pressed. Boost: %s, CanUse: %s" % [boost_val, can_use])
|
||||
|
||||
if can_use:
|
||||
powerup_mgr.use_special_effect() # Sets is_attack_mode=true, Does NOT consume boost yet
|
||||
else:
|
||||
# Optional feedback for not ready?
|
||||
pass
|
||||
else:
|
||||
print("[TouchControls] PowerUpManager missing on player")
|
||||
"SpawnBoost":
|
||||
var powerup_mgr = local_player.get_node_or_null("PowerUpManager")
|
||||
if powerup_mgr:
|
||||
var can_use = powerup_mgr.can_use_special()
|
||||
var boost_val = powerup_mgr.current_boost
|
||||
print("[TouchControls] SpawnBoost Pressed. Boost: %s, CanUse: %s" % [boost_val, can_use])
|
||||
|
||||
if can_use: # Check if boost is full
|
||||
if local_player.special_tiles_manager and local_player.special_tiles_manager.has_method("spawn_powerups_around"):
|
||||
local_player.special_tiles_manager.spawn_powerups_around(local_player.current_position)
|
||||
powerup_mgr.reset_boost() # Consume the boost manually since we bypassed use_special_effect
|
||||
else:
|
||||
print("[TouchControls] PowerUpManager missing on player")
|
||||
|
||||
func _on_button_released(button_name: String):
|
||||
var btn: Button
|
||||
@@ -230,6 +258,7 @@ func _on_button_released(button_name: String):
|
||||
"Grab": btn = grab_button
|
||||
"Put": btn = put_button
|
||||
"Special": btn = special_button
|
||||
"SpawnBoost": btn = spawn_boost_button
|
||||
|
||||
if btn:
|
||||
var tween = create_tween()
|
||||
@@ -268,7 +297,8 @@ func _load_settings():
|
||||
var grab_pos = config.get_value("touch_controls", "grab_position", Vector2(-200, -240))
|
||||
var put_pos = config.get_value("touch_controls", "put_position", Vector2(-120, -160))
|
||||
var special_pos = config.get_value("touch_controls", "special_position", Vector2(-200, -80))
|
||||
button_positions = {"grab": grab_pos, "put": put_pos, "special": special_pos}
|
||||
var spawn_boost_pos = config.get_value("touch_controls", "spawn_boost_position", Vector2(-120, -80))
|
||||
button_positions = {"grab": grab_pos, "put": put_pos, "special": special_pos, "spawn_boost": spawn_boost_pos}
|
||||
|
||||
# Apply loaded settings
|
||||
_apply_settings()
|
||||
@@ -286,6 +316,7 @@ func _save_settings():
|
||||
config.set_value("touch_controls", "grab_position", button_positions.grab)
|
||||
config.set_value("touch_controls", "put_position", button_positions.put)
|
||||
config.set_value("touch_controls", "special_position", button_positions.special)
|
||||
config.set_value("touch_controls", "spawn_boost_position", button_positions.spawn_boost)
|
||||
|
||||
var err = config.save(CONFIG_PATH)
|
||||
if err != OK:
|
||||
@@ -331,6 +362,14 @@ func _apply_settings():
|
||||
special_button.offset_right = button_positions.special.x + button_size
|
||||
special_button.offset_bottom = button_positions.special.y + button_size
|
||||
|
||||
if spawn_boost_button:
|
||||
spawn_boost_button.visible = buttons_visible
|
||||
spawn_boost_button.scale = Vector2(button_scale, button_scale)
|
||||
spawn_boost_button.offset_left = button_positions.spawn_boost.x
|
||||
spawn_boost_button.offset_top = button_positions.spawn_boost.y
|
||||
spawn_boost_button.offset_right = button_positions.spawn_boost.x + button_size
|
||||
spawn_boost_button.offset_bottom = button_positions.spawn_boost.y + button_size
|
||||
|
||||
# Force layer update
|
||||
visible = true
|
||||
|
||||
@@ -375,6 +414,12 @@ func set_button_position(button_name: String, new_position: Vector2):
|
||||
special_button.offset_top = new_position.y
|
||||
special_button.offset_right = new_position.x + button_size
|
||||
special_button.offset_bottom = new_position.y + button_size
|
||||
"spawn_boost":
|
||||
if spawn_boost_button:
|
||||
spawn_boost_button.offset_left = new_position.x
|
||||
spawn_boost_button.offset_top = new_position.y
|
||||
spawn_boost_button.offset_right = new_position.x + button_size
|
||||
spawn_boost_button.offset_bottom = new_position.y + button_size
|
||||
|
||||
func get_button_positions() -> Dictionary:
|
||||
"""Get current button positions for settings UI."""
|
||||
|
||||
@@ -271,7 +271,10 @@ func setup_powerup_bar_ui(main_node):
|
||||
|
||||
func update_powerup_bar(current_points: int, _max_points: int):
|
||||
"""Update battery segments based on current power-up points."""
|
||||
var bars_filled = current_points / 4 # 4 points per bar
|
||||
# 4 Segments total. Max Boost is 100. So each segment represents 25 points.
|
||||
# Was previously dividing by 4, causing it to fill at 16 points!
|
||||
var points_per_segment = _max_points / 4.0
|
||||
var bars_filled = int(current_points / points_per_segment)
|
||||
|
||||
for i in range(powerup_segments.size()):
|
||||
var segment = powerup_segments[i]
|
||||
@@ -289,7 +292,8 @@ func update_powerup_bar(current_points: int, _max_points: int):
|
||||
var _previous_bars: int = 0
|
||||
|
||||
func _on_powerup_points_changed(current: int, max_points: int):
|
||||
var new_bars = current / 4
|
||||
# Calculate based on max points (100) / 4 segments = 25 points per segment
|
||||
var new_bars = int(current / 25.0)
|
||||
|
||||
# Detect if a new bar was filled
|
||||
if new_bars > _previous_bars and powerup_bar:
|
||||
|
||||
Reference in New Issue
Block a user