feat: Add core player entity with movement, race, input, character selection, and multiplayer synchronization, integrating various game managers.

This commit is contained in:
Yogi Wiguna
2026-02-23 15:57:41 +08:00
parent 8a5c25be23
commit 3a5e6ad703
4 changed files with 54 additions and 66 deletions
+1 -1
View File
@@ -282,7 +282,7 @@ func randomize_floor(floor_index: int, custom_rng_callable: Callable = Callable(
# IMPORTANT: Only place items if Floor 0 has a valid ground tile (Walkable, Safe Zone, etc) # IMPORTANT: Only place items if Floor 0 has a valid ground tile (Walkable, Safe Zone, etc)
var floor_0_item = get_cell_item(Vector3i(x, 0, z)) var floor_0_item = get_cell_item(Vector3i(x, 0, z))
var is_ground = (floor_0_item != -1) # All tiles on Layer 0 are valid ground var is_ground = (floor_0_item != -1 and not floor_0_item in non_walkable_items)
if not is_ground: if not is_ground:
set_cell_item(Vector3i(x, floor_index, z), -1) # Clear item if no ground set_cell_item(Vector3i(x, floor_index, z), -1) # Clear item if no ground
+3 -2
View File
@@ -1594,13 +1594,14 @@ func _on_global_timer_updated(time_remaining: float):
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func sync_game_end_stop_n_go(winner_id: int): func sync_game_end_stop_n_go(winner_id: int):
print("[STOP n GO] Game ended! Winner: ", winner_id) print("[STOP n GO] Game ended! Winner: ", winner_id)
var winner_name = "Player " + str(winner_id) var winner_name = "Player " + str(winner_id)
var player_node = get_node_or_null(str(winner_id)) var player_node = get_node_or_null(str(winner_id))
if player_node: if player_node:
winner_name = player_node.display_name winner_name = player_node.display_name
# Broadcast win # Broadcast win (Validation already done in check_win_condition)
add_message_to_bar("WINNER", winner_name + " Reached the Finish Line!", MessageType.GOAL) add_message_to_bar("MATCH COMPLETE", winner_name + " Wins with 3 Missions!", MessageType.GOAL)
# Stop logic # Stop logic
if stop_n_go_manager: if stop_n_go_manager:
+5 -4
View File
@@ -1311,10 +1311,11 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
# This ensures that when interpolation resumes (in _process), it pulls to the correct spot # This ensures that when interpolation resumes (in _process), it pulls to the correct spot
target_visual_position = grid_to_world(current_position) target_visual_position = grid_to_world(current_position)
# Check if we've reached the finish line (uses lap-aware finish locations) # Racing Win Check (Skipped in Stop n Go which uses its own block above)
var current_finish_locs = race_manager.get_current_finish_locations() if race_manager else finish_locations if LobbyManager.game_mode != "Stop n Go":
if current_position in current_finish_locs and can_finish: var current_finish_locs = race_manager.get_current_finish_locations() if race_manager else finish_locations
finish_race() if current_position in current_finish_locs and can_finish:
finish_race()
var main = get_tree().get_root().get_node_or_null("Main") var main = get_tree().get_root().get_node_or_null("Main")
+45 -59
View File
@@ -105,21 +105,20 @@ func _update_hud_visuals():
red_tint_overlay.visible = (current_phase == Phase.STOP) red_tint_overlay.visible = (current_phase == Phase.STOP)
var my_id = multiplayer.get_unique_id() var my_id = multiplayer.get_unique_id()
if mission_label and player_missions.has(my_id): if mission_label:
var mission = player_missions[my_id] var main = get_node_or_null("/root/Main")
# Get Icon name or ID for display var goals_cycle_manager = main.get_node_or_null("GoalsCycleManager") if main else null
var tile_name = "Items"
match mission["target_tile"]:
7: tile_name = "Hearts"
8: tile_name = "Diamonds"
9: tile_name = "Stars"
10: tile_name = "Coins"
mission_label.text = "Collect %d %s: %d / %d" % [mission["required"], tile_name, mission["current"], mission["required"]]
if mission["current"] >= mission["required"]: # Get count from GoalsCycleManager (Source of truth for PlayerBoardLabel)
mission_label.text = "MISSION COMPLETE! REACH FINISH!" var completed_count = goals_cycle_manager.player_goal_counts.get(my_id, 0) if goals_cycle_manager else 0
mission_label.text = "GOALS (%d/3)" % completed_count
if completed_count >= 3:
mission_label.text = "ALL GOALS COMPLETE!\nREACH THE FINISH!"
mission_label.add_theme_color_override("font_color", Color.GOLD) mission_label.add_theme_color_override("font_color", Color.GOLD)
else:
mission_label.add_theme_color_override("font_color", Color.WHITE)
func activate_client_side(): func activate_client_side():
is_active = true is_active = true
@@ -284,22 +283,9 @@ func _spawn_mission_tiles():
count += 1 count += 1
func _assign_missions(): func _assign_missions():
# Assign UNIQUE target tile types to players cyclicly # NO-OP: Missions are now achievement-based (Complete 3 Goals)
var players = GameStateManager.players # which is tracked natively by GoalsCycleManager.
var tile_types = [ScarcityModel.TILE_HEART, ScarcityModel.TILE_DIAMOND, ScarcityModel.TILE_STAR, ScarcityModel.TILE_COIN] pass
var idx = 0
for p_id in players:
var target = tile_types[idx % tile_types.size()]
player_missions[p_id] = {
"target_tile": target,
"required": 3,
"current": 0
}
idx += 1
if can_rpc():
rpc("sync_missions", player_missions)
@rpc("authority", "call_local", "reliable") @rpc("authority", "call_local", "reliable")
func sync_missions(missions: Dictionary): func sync_missions(missions: Dictionary):
@@ -338,38 +324,38 @@ func _penalize_player(player_id: int):
# Notification is also handled inside on_stop_phase_violation on the player node # Notification is also handled inside on_stop_phase_violation on the player node
emit_signal("player_penalized", player_id) emit_signal("player_penalized", player_id)
func update_mission_progress(player_id: int, tile_id: int): func update_mission_progress(_player_id: int, _tile_id: int):
if not player_missions.has(player_id): return # Redundant in Board-based mode, but kept for compatibility.
# The board is synced separately via sync_playerboard in playerboard_manager.gd.
var mission = player_missions[player_id] pass
if tile_id == mission["target_tile"]:
mission["current"] = min(mission["current"] + 1, mission["required"])
if mission["current"] >= mission["required"]:
emit_signal("mission_status_updated", player_id, true)
# FIX: NotificationManager.send_message_to_player() does NOT exist.
# We need to find the player node and use send_message(target, msg, type)
var main = get_node("/root/Main")
if main:
var player_node = main.get_node_or_null(str(player_id))
if player_node:
NotificationManager.send_message(player_node, "Mission Complete! Reach the Finish!", NotificationManager.MessageType.GOAL)
if multiplayer.is_server() and can_rpc():
rpc("sync_mission_progress", player_id, mission["current"])
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func sync_mission_progress(player_id: int, current: int): func sync_mission_progress(_player_id: int, _mission_index: int, _current: int):
if player_missions.has(player_id): # Deprecated
player_missions[player_id]["current"] = current pass
func check_win_condition(player_id: int, position: Vector2i) -> bool: func check_win_condition(player_id: int, position: Vector2i) -> bool:
if not player_missions.has(player_id): return false # 1. Must reach the finish line (Column 21)
if position.x < finish_line_x:
return false
# 2. Must have 3 Goal Completions (tracked by GoalsCycleManager)
var main = get_node_or_null("/root/Main")
if not main: return false
var mission = player_missions[player_id] var goals_cycle_manager = main.get_node_or_null("GoalsCycleManager")
if mission["current"] >= mission["required"]: if not goals_cycle_manager: return false
# Win when reaching X >= 21
if position.x >= finish_line_x: var completed_count = goals_cycle_manager.player_goal_counts.get(player_id, 0)
return true
return false if completed_count >= 3:
print("[StopNGo] Player %d REACHED FINISH with %d goals complete!" % [player_id, completed_count])
return true
else:
# Inform the player locally if they reach the end without goals
var player_node = main.get_node_or_null(str(player_id))
if player_node:
NotificationManager.send_message(player_node, "Incomplete! Achieve 3 goals (x3) to win!", NotificationManager.MessageType.WARNING)
print("[StopNGo] Player %d reached finish but goal count too low: %d/3" % [player_id, completed_count])
return false