feat: Add core player entity with movement, race, input, character selection, and multiplayer synchronization, integrating various game managers.
This commit is contained in:
@@ -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
@@ -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
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user