feat: Create player input and powerup managers to handle player interactions, movement, actions, targeting, and boost meter mechanics.

This commit is contained in:
Yogi Wiguna
2026-03-05 14:29:34 +08:00
parent ebfa8f99a7
commit fb6d9df5db
7 changed files with 51 additions and 48 deletions
+23 -33
View File
@@ -807,7 +807,7 @@ func _assign_random_spawn_positions():
print("Assigned spawn %s to player %s" % [assigned_pos, player.name])
func _assign_stop_n_go_spawn_positions(all_players: Array):
"""Assigns random spawns on walkable arena tiles for Stop N Go mode."""
"""Assigns spawns on column 0, starting from row 1 for Stop N Go mode."""
# Sort players for deterministic assignment based on ID
all_players.sort_custom(func(a, b): return a.name.to_int() < b.name.to_int())
@@ -815,33 +815,12 @@ func _assign_stop_n_go_spawn_positions(all_players: Array):
if not enhanced_gridmap:
return
# Collect all valid walkable positions (not obstacle, not void)
var valid_positions: Array[Vector2i] = []
for x in range(enhanced_gridmap.columns):
for z in range(enhanced_gridmap.rows):
var tile = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z))
# Accept walkable (0), start (3), finish (3) — skip obstacles (4) and void (-1)
if tile != -1 and tile != 4:
valid_positions.append(Vector2i(x, z))
valid_positions.shuffle()
var used_positions: Array[Vector2i] = []
var col = 0
var current_row = 1
for player in all_players:
var assigned_pos = Vector2i(-1, -1)
# Find a random walkable position not occupied by another player
for pos in valid_positions:
if pos not in used_positions:
assigned_pos = pos
break
if assigned_pos == Vector2i(-1, -1):
# Fallback: center of arena
assigned_pos = Vector2i(int(enhanced_gridmap.columns / 2), int(enhanced_gridmap.rows / 2))
used_positions.append(assigned_pos)
var assigned_pos = Vector2i(col, current_row)
current_row += 1
# Ensure immediate sync
player.position = player.grid_to_world(assigned_pos)
@@ -851,7 +830,7 @@ func _assign_stop_n_go_spawn_positions(all_players: Array):
if can_rpc():
player.rpc("set_spawn_position", assigned_pos)
print("[StopNGo] Assigned random spawn %s to player %s" % [assigned_pos, player.name])
print("[StopNGo] Assigned spawn %s to player %s" % [assigned_pos, player.name])
func _assign_portal_mode_spawn_positions(all_players: Array):
"""Assigns spawns to different quadrants for Tekton Doors mode."""
@@ -1584,14 +1563,23 @@ func randomize_item_at_position(grid_position: Vector2i):
if is_ground:
var new_item = 7
# Use ScarcityController
new_item = ScarcityController.get_random_tile_id()
var get_mode_specific_tile = func():
if LobbyManager.game_mode != "Stop n Go" and LobbyManager.game_mode != "Tekton Doors":
# 60% Chance for Common (7-10), 40% for PowerUp
if randf() <= 0.6:
return [7, 8, 9, 10].pick_random()
else:
return ScarcityModel.SPECIAL_TILES.pick_random()
return ScarcityController.get_random_tile_id()
new_item = get_mode_specific_tile.call()
# If we are replacing an existing item, try to ensure it changes
if current_item != -1:
var max_retries = 3
while new_item == current_item and max_retries > 0:
new_item = ScarcityController.get_random_tile_id()
new_item = get_mode_specific_tile.call()
max_retries -= 1
sync_grid_item(cell.x, cell.y, cell.z, new_item)
@@ -1654,10 +1642,12 @@ func randomize_game_grid():
var enhanced_gridmap = $EnhancedGridMap
if enhanced_gridmap:
# Use density-aware callable: 60% chance for a real tile, 40% for none
# Custom spawn ratio for Free Mode: 60% common tiles, 40% empty tiles (start of game)
var density_callable = func():
if randf() > 0.6: return -1
return ScarcityController.get_random_tile_id()
if randf() <= 0.6:
return ScarcityModel.STANDARD_TILES.pick_random()
else:
return -1 # Empty
enhanced_gridmap.randomize_floor(1, density_callable)
+5
View File
@@ -2084,6 +2084,11 @@ func sync_grab_tekton(tekton_path: NodePath):
carried_tekton = tekton
is_carrying_tekton = true
tekton.set_carried(true, self )
# Disposed of AttackMode upon grab
if is_attack_mode:
is_attack_mode = false
print("[Player %s] Grabbed Tekton %s" % [name, tekton.name])
func throw_tekton():
+3 -2
View File
@@ -125,8 +125,9 @@ func handle_unhandled_input(event):
if player.powerup_manager.has_method("spawn_boost_reward"):
player.powerup_manager.spawn_boost_reward()
KEY_G:
if not player.is_carrying_tekton:
player.grab_tekton()
if not player.is_carrying_tekton and player.powerup_manager:
if player.powerup_manager.can_use_special():
player.grab_tekton()
# Handle spawn point selection if not yet selected
+9 -3
View File
@@ -156,6 +156,11 @@ func use_special_effect() -> bool:
if not can_use_special():
return false
# Restriction: Cannot use attack mode while carrying a Tekton
if player.is_carrying_tekton:
NotificationManager.send_message(player, "Cannot enter Attack Mode while carrying a Tekton!", NotificationManager.MessageType.WARNING)
return false
# Enable Attack Mode explicitly
player.is_attack_mode = true
# Do NOT consume boost here. Boost acts as "fuel" for the attacks.
@@ -183,14 +188,15 @@ func spawn_boost_reward() -> bool:
return false
if player.special_tiles_manager and player.special_tiles_manager.has_method("spawn_powerups_around"):
# Spawn only common tiles (7-10) with 100% density
player.special_tiles_manager.spawn_powerups_around(player.current_position, true, true, true)
# 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()
print("[PowerUp] %s used Tekton to SPAWN TILES (100%% Density Common)." % player.name)
reset_boost()
print("[PowerUp] %s used Tekton to SPAWN TILES and consumed Boost." % player.name)
return true
return false
+3 -3
View File
@@ -441,14 +441,14 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true, only_c
var mode = LobbyManager.get_game_mode()
if only_common or LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO):
# Spawn ONLY goals (7-10) for Stop n Go or if forced
# Spawn ONLY common tiles (7-10) for Stop n Go or if forced
item_id = rng.randi_range(7, 10)
else:
# Free mode: 60% Chance for Normal Tile (7-10), 40% for PowerUp
# Free mode: 60% Chance for Common Tile (7-10), 40% for PowerUp
if rng.randf() < 0.6:
item_id = rng.randi_range(7, 10)
else:
# 30% Chance for PowerUp (Speed 11, Freeze 12, Ghost 14 - Exclude Wall 13 in restricted modes)
# 40% Chance for PowerUp
var is_restricted = GameMode.is_restricted(mode)
if is_restricted:
item_id = [11, 14].pick_random()
+6 -2
View File
@@ -524,13 +524,17 @@ func _on_boost_points_changed(current_points: int, max_points: int):
# User Request: Disable Special & SpawnBoost if < 100%
var is_full = current_points >= (max_points - 1) # Tolerance
_update_boost_button_state(attack_mode_button, is_full)
# Attack Mode (⚡) is only enabled if full AND not carrying a Tekton
var can_attack = is_full and not (local_player and local_player.is_carrying_tekton)
_update_boost_button_state(attack_mode_button, can_attack)
# SpawnBoost depends on carrying a Tekton, not boost points
var can_spawn = local_player and local_player.is_carrying_tekton
_update_boost_button_state(spawn_boost_button, can_spawn)
_update_boost_button_state(tekton_grab_button, true) # Always enabled (logic handles grab-ability)
# Tekton Grab (👋) is only enabled if full AND not already carrying one
var can_grab = is_full and not (local_player and local_player.is_carrying_tekton)
_update_boost_button_state(tekton_grab_button, can_grab)
func _on_tekton_carried_changed(_is_carrying: bool):
# Refresh button states when player grabs/throws a tekton
+2 -5
View File
@@ -387,12 +387,9 @@ func spawn_tiles_around(count: int = 4):
if roll < 0.6:
# 60% Normal Tile (7-10)
item_id = rng.randi_range(7, 10)
elif roll < 0.9:
# 30% PowerUp (11-14)
item_id = rng.randi_range(11, 14)
else:
# 10% Obstacle/Trap (optional)
item_id = -1 # Clear?
# 40% PowerUp (11-14)
item_id = rng.randi_range(11, 14)
if item_id != -1:
var main = get_tree().get_root().get_node_or_null("Main")