feat: introduce core player logic with manager integration and power-up inventory UI.
This commit is contained in:
+17
-10
@@ -723,7 +723,6 @@ func apply_stagger(duration: float = 1.5):
|
||||
|
||||
if is_multiplayer_authority():
|
||||
NotificationManager.send_message(self , NotificationManager.MESSAGES.CRUSHED, NotificationManager.MessageType.WARNING)
|
||||
drop_random_item()
|
||||
|
||||
# Grant "Smashed" Bonus (1 bar, max 2)
|
||||
if powerup_manager:
|
||||
@@ -1140,15 +1139,16 @@ func handle_grid_click(grid_position: Vector2i):
|
||||
|
||||
# Modify is_position_occupied to check for selected spawn points
|
||||
func is_position_occupied(pos: Vector2i) -> bool:
|
||||
for player in get_tree().get_nodes_in_group("Players"):
|
||||
if player == self:
|
||||
for p in get_tree().get_nodes_in_group("Players"):
|
||||
if p == self:
|
||||
continue
|
||||
|
||||
if player.spawn_point_selected and player.current_position == pos:
|
||||
# Check if player has selected a spawn point OR is already visible (active in match)
|
||||
if (p.spawn_point_selected or p.visible) and p.current_position == pos:
|
||||
return true
|
||||
|
||||
# Check target position (where they are moving to)
|
||||
if player.is_player_moving and player.target_position == pos:
|
||||
if p.is_player_moving and p.target_position == pos:
|
||||
return true
|
||||
|
||||
return false
|
||||
@@ -1336,12 +1336,16 @@ func simple_move_to(grid_position: Vector2i):
|
||||
func move_player_to_clicked_position(grid_position: Vector2i):
|
||||
movement_manager.move_to_clicked_position(grid_position)
|
||||
|
||||
@rpc("any_peer", "call_remote", "unreliable")
|
||||
func start_movement_along_path(path: Array, clear_visual: bool = true):
|
||||
if is_player_moving:
|
||||
return # ALREADY MOVING. Guard against redundant RPCs or interruptions.
|
||||
@rpc("any_peer", "call_remote", "reliable")
|
||||
func start_movement_along_path(path: Array, clear_visual: bool = true, force: bool = false):
|
||||
if is_player_moving and not force:
|
||||
return # ALREADY MOVING. Guard against redundant RPCs or interruptions unless forced.
|
||||
|
||||
print("[Player] %s starting move along path: %s" % [name, path])
|
||||
if force and movement_manager:
|
||||
movement_manager.movement_queue.clear()
|
||||
movement_manager.current_move_direction = Vector2i.ZERO
|
||||
|
||||
print("[Player] %s starting move along path: %s (Forced: %s)" % [name, path, force])
|
||||
|
||||
# SERVER-SIDE VIOLATION CHECK (for Stop n Go)
|
||||
if multiplayer.is_server() and LobbyManager.game_mode == "Stop n Go":
|
||||
@@ -1797,6 +1801,9 @@ func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int
|
||||
# Add new RPC function to notify others about spawn selection
|
||||
@rpc("any_peer", "reliable")
|
||||
func notify_spawn_selected(spawn_pos: Vector2i):
|
||||
# Mark as selected on all peers so occupancy checks work
|
||||
spawn_point_selected = true
|
||||
|
||||
# Update local highlight state for all clients
|
||||
if spawn_pos in highlighted_spawn_points:
|
||||
highlighted_spawn_points.erase(spawn_pos)
|
||||
|
||||
@@ -95,24 +95,16 @@ func handle_unhandled_input(event):
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4:
|
||||
var mode = LobbyManager.get_game_mode()
|
||||
var is_restricted = GameMode.is_restricted(mode)
|
||||
if is_restricted:
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1:
|
||||
player.activate_powerup(0) # FASTER_SPEED
|
||||
KEY_KP_2, KEY_2:
|
||||
player.activate_powerup(3) # INVISIBLE_MODE (Ghost is now 2)
|
||||
else:
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1:
|
||||
player.activate_powerup(0) # FASTER_SPEED
|
||||
KEY_KP_2, KEY_2:
|
||||
player.activate_powerup(2) # BLOCK_FLOOR
|
||||
KEY_KP_3, KEY_3:
|
||||
player.activate_powerup(1) # AREA_FREEZE
|
||||
KEY_KP_4, KEY_4:
|
||||
player.activate_powerup(3) # INVISIBLE_MODE
|
||||
# Unified Mapping for all modes: 1-Speed, 2-Wall, 3-Freeze, 4-Ghost
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1:
|
||||
player.activate_powerup(0) # FASTER_SPEED
|
||||
KEY_KP_2, KEY_2:
|
||||
player.activate_powerup(2) # BLOCK_FLOOR
|
||||
KEY_KP_3, KEY_3:
|
||||
player.activate_powerup(1) # AREA_FREEZE
|
||||
KEY_KP_4, KEY_4:
|
||||
player.activate_powerup(3) # INVISIBLE_MODE
|
||||
# KEY_R:
|
||||
# player.auto_put_item()
|
||||
KEY_Q:
|
||||
|
||||
@@ -24,6 +24,7 @@ func initialize(p_player: Node3D, p_gridmap: Node):
|
||||
signal movement_finished
|
||||
var movement_queue: Array[Vector2i] = [] # Queue of target grid positions
|
||||
var current_move_direction: Vector2i = Vector2i.ZERO
|
||||
var last_move_direction: Vector2i = Vector2i(0, 1) # Default forward (towards +Z)
|
||||
|
||||
func _process(delta):
|
||||
if player:
|
||||
@@ -137,6 +138,8 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
path.pop_front()
|
||||
|
||||
current_move_direction = grid_position - player.current_position
|
||||
if current_move_direction != Vector2i.ZERO:
|
||||
last_move_direction = current_move_direction
|
||||
|
||||
if player.is_multiplayer_authority():
|
||||
# Authority starts their own tween locally
|
||||
@@ -174,54 +177,39 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
|
||||
NotificationManager.send_message(player, "Cannot Attack in Safe Zone!", NotificationManager.MessageType.WARNING)
|
||||
return false
|
||||
|
||||
# 1. Drop Victim's Tiles
|
||||
if other_player.has_method("drop_all_tiles"):
|
||||
if _can_rpc():
|
||||
other_player.rpc("drop_all_tiles") # Sync drop
|
||||
# 1. 3-Floor Knockback towards Starting Line (X=0)
|
||||
var push_direction = Vector2i(-1, 0) # Backwards
|
||||
var pushed_to_pos = target_pos
|
||||
var push_path = []
|
||||
|
||||
# 2. Spawn PowerUps around Victim
|
||||
# We delegate this to the attacker's SpecialTilesManager to handle the spawning authority
|
||||
if player.special_tiles_manager and player.special_tiles_manager.has_method("spawn_powerups_around"):
|
||||
player.special_tiles_manager.spawn_powerups_around(other_player.current_position)
|
||||
|
||||
# 3. Knockback / Stagger
|
||||
# Push them away
|
||||
var pushed_to_pos = target_pos + direction
|
||||
|
||||
# IMPROVED: Check if destination is valid and walkable to prevent being stuck on 'blocks'
|
||||
var is_dest_valid = _can_push_to(pushed_to_pos)
|
||||
|
||||
# DEFLECTION LOGIC: If direct path is blocked, try diagonal deflection
|
||||
if not is_dest_valid:
|
||||
var alts = []
|
||||
if direction.x != 0 and direction.y == 0: # Horizontal push -> try diagonal North/South
|
||||
alts = [pushed_to_pos + Vector2i(0, 1), pushed_to_pos + Vector2i(0, -1)]
|
||||
elif direction.y != 0 and direction.x == 0: # Vertical push -> try diagonal East/West
|
||||
alts = [pushed_to_pos + Vector2i(1, 0), pushed_to_pos + Vector2i(-1, 0)]
|
||||
elif direction.x != 0 and direction.y != 0: # Diagonal push -> try horizontal/vertical components
|
||||
alts = [pushed_to_pos - Vector2i(direction.x, 0), pushed_to_pos - Vector2i(0, direction.y)]
|
||||
# Try to push up to 3 tiles back, building the path as we go
|
||||
for i in range(3):
|
||||
var next_back = pushed_to_pos + push_direction
|
||||
if _can_push_to(next_back):
|
||||
pushed_to_pos = next_back
|
||||
push_path.append(Vector2(pushed_to_pos.x, pushed_to_pos.y))
|
||||
else:
|
||||
break # Blocked by wall or edge
|
||||
|
||||
for alt in alts:
|
||||
if _can_push_to(alt):
|
||||
pushed_to_pos = alt
|
||||
is_dest_valid = true
|
||||
break
|
||||
|
||||
if is_dest_valid:
|
||||
# Valid push
|
||||
var push_path = [Vector2(pushed_to_pos.x, pushed_to_pos.y)]
|
||||
if push_path.size() > 0:
|
||||
# Valid push movement
|
||||
if _can_rpc():
|
||||
other_player.rpc("start_movement_along_path", push_path, false)
|
||||
# Pass 'true' for 'force' parameter to interrupt active movements
|
||||
other_player.rpc("start_movement_along_path", push_path, false, true)
|
||||
|
||||
# Authority Check: If we are already the authority for the victim (e.g. Host hitting a Bot),
|
||||
# the 'call_remote' RPC above won't execute locally. We MUST call it manually.
|
||||
if other_player.is_multiplayer_authority():
|
||||
other_player.start_movement_along_path(push_path, false, true)
|
||||
|
||||
other_player.target_position = pushed_to_pos # Logical update
|
||||
|
||||
# Apply stun/freeze effect as requested (same as wall stagger)
|
||||
if _can_rpc():
|
||||
other_player.rpc("apply_stagger", 1.5)
|
||||
|
||||
# 2. Apply freeze/stun effect (blue tint)
|
||||
if _can_rpc():
|
||||
other_player.rpc("apply_stagger", 1.5)
|
||||
else:
|
||||
# Wall/Blocked -> Stagger in place (Only if no alternatives found)
|
||||
if _can_rpc():
|
||||
other_player.rpc("apply_stagger", 1.5)
|
||||
# Handle local execution (e.g. offline or host-only logic)
|
||||
other_player.apply_stagger(1.5)
|
||||
|
||||
# 4. Consume Boost (Full) - One hit per charge
|
||||
if player.powerup_manager:
|
||||
@@ -246,12 +234,8 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
|
||||
else:
|
||||
NotificationManager.send_message(player, "Successful Attack!", NotificationManager.MessageType.GOAL)
|
||||
|
||||
# 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
|
||||
# 5. Block the attacker from moving into the victim's space to prevent overlapping
|
||||
return false
|
||||
|
||||
func set_speed_multiplier(multiplier: float):
|
||||
speed_multiplier = multiplier
|
||||
|
||||
@@ -26,7 +26,6 @@ const FREEZE_SLOW_DURATION = 3.0
|
||||
signal cooldown_updated(effect: int, time_left: float, max_time: float)
|
||||
signal powerup_unlocked(effect: int, level: int)
|
||||
|
||||
var wall_orientation_horizontal: bool = false # False = Vertical, True = Horizontal
|
||||
|
||||
# New Helper functions for Targeting and Preview
|
||||
|
||||
@@ -35,28 +34,12 @@ func get_skill_affected_area(effect: int, center_pos: Vector2i) -> Array[Vector2
|
||||
|
||||
match effect:
|
||||
SpecialEffect.AREA_FREEZE:
|
||||
var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1)
|
||||
var radius = 1
|
||||
if current_lvl >= 5:
|
||||
radius = 2
|
||||
|
||||
for x in range(-radius, radius + 1):
|
||||
for y in range(-radius, radius + 1):
|
||||
var pos = center_pos + Vector2i(x, y)
|
||||
# Validate bounds
|
||||
if enhanced_gridmap.is_position_valid(pos):
|
||||
area.append(pos)
|
||||
# Preview 3 blocks ahead of current hover (if we ever re-enable targeting)
|
||||
area.append(center_pos)
|
||||
|
||||
SpecialEffect.BLOCK_FLOOR:
|
||||
# Logic: Based on toggled orientation state
|
||||
var is_horizontal = wall_orientation_horizontal
|
||||
|
||||
if is_horizontal:
|
||||
for x in range(enhanced_gridmap.columns):
|
||||
area.append(Vector2i(x, center_pos.y))
|
||||
else:
|
||||
for z in range(enhanced_gridmap.rows):
|
||||
area.append(Vector2i(center_pos.x, z))
|
||||
# Preview just the single block
|
||||
area.append(center_pos)
|
||||
|
||||
return area
|
||||
|
||||
@@ -143,12 +126,8 @@ func get_effect_from_item(item_id: int) -> int:
|
||||
|
||||
match item_id:
|
||||
11: return SpecialEffect.FASTER_SPEED
|
||||
12:
|
||||
if is_restricted: return -1
|
||||
return SpecialEffect.AREA_FREEZE
|
||||
13:
|
||||
if is_restricted: return -1
|
||||
return SpecialEffect.BLOCK_FLOOR
|
||||
12: return SpecialEffect.AREA_FREEZE
|
||||
13: return SpecialEffect.BLOCK_FLOOR
|
||||
14: return SpecialEffect.INVISIBLE_MODE
|
||||
_: return -1
|
||||
|
||||
@@ -231,32 +210,11 @@ func activate_effect(effect: int, target_player: Node3D = null):
|
||||
SpecialEffect.FASTER_SPEED:
|
||||
_execute_faster_speed()
|
||||
SpecialEffect.AREA_FREEZE, SpecialEffect.BLOCK_FLOOR:
|
||||
# Enter Targeting Mode instead of executing immediately (ONLY for human players)
|
||||
if not (player.is_bot or player.is_in_group("Bots")):
|
||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||
if main and main.ui_manager:
|
||||
# Toggle Logic for Wall Block
|
||||
if main.ui_manager.current_action_state == main.ui_manager.ActionState.TARGETING and main.ui_manager.pending_skill_id == effect:
|
||||
if effect == SpecialEffect.BLOCK_FLOOR:
|
||||
toggle_wall_orientation()
|
||||
powerup_cooldowns[effect] = 0.0 # Revert cooldown
|
||||
emit_signal("cooldown_updated", effect, 0.0, 0.0)
|
||||
return
|
||||
|
||||
main.ui_manager.current_action_state = main.ui_manager.ActionState.TARGETING
|
||||
main.ui_manager.pending_skill_id = effect
|
||||
|
||||
var msg = "Select a target area..."
|
||||
if effect == SpecialEffect.BLOCK_FLOOR:
|
||||
msg = "Click again to toggle Vertical/Horizontal"
|
||||
|
||||
NotificationManager.send_message(player, msg, NotificationManager.MessageType.NORMAL)
|
||||
# Do NOT set cooldown yet. Cooldown sets on execution.
|
||||
# Revert the cooldown set above (hacky but handles the split flow)
|
||||
powerup_cooldowns[effect] = 0.0
|
||||
emit_signal("cooldown_updated", effect, 0.0, 0.0)
|
||||
print("[SpecialTiles] Entered Targeting Mode for %s" % SpecialEffect.keys()[effect])
|
||||
return # Exit, wait for input
|
||||
# Execute immediately based on direction instead of entering Targeting Mode
|
||||
if effect == SpecialEffect.BLOCK_FLOOR:
|
||||
_execute_block_floor()
|
||||
else:
|
||||
_execute_area_freeze()
|
||||
SpecialEffect.INVISIBLE_MODE:
|
||||
_execute_invisible_mode(player)
|
||||
|
||||
@@ -284,13 +242,33 @@ func _execute_faster_speed():
|
||||
active_buffs[SpecialEffect.FASTER_SPEED] = FASTER_DURATION
|
||||
NotificationManager.send_message(player, "Speed Boost! (5s)", NotificationManager.MessageType.POWERUP)
|
||||
|
||||
func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
|
||||
func _execute_area_freeze(target_pos: Vector2i = Vector2i.ZERO):
|
||||
var center_pos = target_pos
|
||||
|
||||
# Determine Level early for distance calculation
|
||||
var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1)
|
||||
|
||||
if center_pos == Vector2i.ZERO:
|
||||
# Fallback to old behavior if no target provided (or error)
|
||||
return
|
||||
# Calculate distance ahead based on Level
|
||||
# Gap of 3 floors = 4 tiles ahead
|
||||
# Gap of 5 floors = 6 tiles ahead
|
||||
var distance = 4 if current_lvl < 5 else 6
|
||||
|
||||
var movement = player.movement_manager
|
||||
if movement and movement.current_move_direction != Vector2i.ZERO:
|
||||
center_pos = player.current_position + movement.current_move_direction * distance
|
||||
else:
|
||||
# Fallback if standing still
|
||||
var last_dir = player.movement_manager.last_move_direction if movement else Vector2i(0, 1)
|
||||
center_pos = player.current_position + last_dir * distance
|
||||
|
||||
if not enhanced_gridmap.is_position_valid(center_pos):
|
||||
# Try a bit closer if out of bounds
|
||||
center_pos = player.current_position + (center_pos - player.current_position).normalized() * 1.0
|
||||
if not enhanced_gridmap.is_position_valid(center_pos):
|
||||
return
|
||||
|
||||
# 3. Determine Radius based on Level
|
||||
var current_lvl = powerup_levels.get(SpecialEffect.AREA_FREEZE, 1)
|
||||
var radius = 1
|
||||
if current_lvl >= 5:
|
||||
radius = 2 # Bigger area at high levels
|
||||
@@ -366,29 +344,29 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
|
||||
main.rpc("sync_grid_items_batch", restore_batch)
|
||||
)
|
||||
|
||||
func toggle_wall_orientation():
|
||||
wall_orientation_horizontal = !wall_orientation_horizontal
|
||||
var mode_str = "HORIZONTAL" if wall_orientation_horizontal else "VERTICAL"
|
||||
NotificationManager.send_message(player, "Wall Mode: " + mode_str, NotificationManager.MessageType.NORMAL)
|
||||
|
||||
func _execute_block_floor(target_pos: Vector2i):
|
||||
# "Wall Block"
|
||||
var is_horizontal = wall_orientation_horizontal
|
||||
func _execute_block_floor(target_pos: Vector2i = Vector2i.ZERO):
|
||||
# "Wall Block" - Spawn line behind player
|
||||
var behind_pos = target_pos
|
||||
var last_dir = player.movement_manager.last_move_direction if player.movement_manager else Vector2i(0, 1)
|
||||
|
||||
if behind_pos == Vector2i.ZERO:
|
||||
behind_pos = player.current_position - last_dir
|
||||
|
||||
if not enhanced_gridmap.is_position_valid(behind_pos):
|
||||
return
|
||||
|
||||
print("Player %s activated Wall Block behind at %s" % [player.name, behind_pos])
|
||||
|
||||
var neighbors = []
|
||||
|
||||
if is_horizontal:
|
||||
# Block entire Row (Fixed Z, iterate all X)
|
||||
var row_z = target_pos.y
|
||||
for x in range(enhanced_gridmap.columns):
|
||||
neighbors.append({"position": Vector2i(x, row_z)})
|
||||
print("Player %s activated Wall Block: HORIZONTAL ROW (Z=%d)" % [player.name, row_z])
|
||||
else:
|
||||
# Block entire Column (Fixed X, iterate all Z)
|
||||
var col_x = target_pos.x
|
||||
if last_dir.x != 0:
|
||||
# Moving on X-axis (Columns) -> Vertical Wall (Fixed X, all Z)
|
||||
for z in range(enhanced_gridmap.rows):
|
||||
neighbors.append({"position": Vector2i(col_x, z)})
|
||||
print("Player %s activated Wall Block: VERTICAL COLUMN (X=%d)" % [player.name, col_x])
|
||||
neighbors.append({"position": Vector2i(behind_pos.x, z)})
|
||||
else:
|
||||
# Moving on Z-axis (Rows) -> Horizontal Wall (Fixed Z, all X)
|
||||
for x in range(enhanced_gridmap.columns):
|
||||
neighbors.append({"position": Vector2i(x, behind_pos.y)})
|
||||
|
||||
if player.is_multiplayer_authority():
|
||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||
@@ -420,10 +398,7 @@ func _execute_block_floor(target_pos: Vector2i):
|
||||
main.rpc("sync_grid_items_batch", batch_data)
|
||||
|
||||
# Notify
|
||||
var all_players = player.get_tree().get_nodes_in_group("Players")
|
||||
for p in all_players:
|
||||
if p.current_position == target_pos:
|
||||
NotificationManager.send_message(p, "Wall Block Created!", NotificationManager.MessageType.POWERUP)
|
||||
NotificationManager.send_message(player, "Defensive Wall Deployed!", NotificationManager.MessageType.POWERUP)
|
||||
|
||||
func _execute_invisible_mode(target: Node3D):
|
||||
target.is_invisible = true
|
||||
|
||||
@@ -40,9 +40,9 @@ func _ready():
|
||||
_setup_btn(2, wall_btn)
|
||||
_setup_btn(3, ghost_btn)
|
||||
|
||||
if is_restricted:
|
||||
if wall_btn: wall_btn.visible = false
|
||||
if freeze_btn: freeze_btn.visible = false
|
||||
# All skills available with new logic
|
||||
if wall_btn: wall_btn.visible = true
|
||||
if freeze_btn: freeze_btn.visible = true
|
||||
|
||||
print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size())
|
||||
|
||||
@@ -111,21 +111,12 @@ func _setup_btn(effect_id: int, btn: Button):
|
||||
|
||||
# Determine Label Text based on Effect ID
|
||||
var key_text = ""
|
||||
var mode = LobbyManager.get_game_mode()
|
||||
var is_restricted = GameMode.is_restricted(mode)
|
||||
if is_restricted:
|
||||
# Restricted Mapping: 1, 2
|
||||
match effect_id:
|
||||
0: key_text = "1"
|
||||
3: key_text = "2" # Ghost is now 2
|
||||
_: key_text = ""
|
||||
else:
|
||||
# Free Mode Mapping: 1, 2, 3, 4 (Original)
|
||||
match effect_id:
|
||||
0: key_text = "1"
|
||||
2: key_text = "2"
|
||||
1: key_text = "3"
|
||||
3: key_text = "4"
|
||||
# Consistent mapping: 1, 2, 3, 4
|
||||
match effect_id:
|
||||
0: key_text = "1" # Speed
|
||||
2: key_text = "2" # Wall
|
||||
1: key_text = "3" # Freeze
|
||||
3: key_text = "4" # Ghost
|
||||
|
||||
sc_lbl.text = key_text
|
||||
btn.add_child(sc_lbl)
|
||||
|
||||
Reference in New Issue
Block a user