feat: Implement core multiplayer features including user authentication, profile management, lobby, game mode managers, and leaderboard.
This commit is contained in:
@@ -19,6 +19,16 @@ signal character_changed(player_id: int, character_name: String)
|
||||
signal area_changed(area_name: String)
|
||||
signal player_list_changed()
|
||||
|
||||
# Stop N Go settings signals
|
||||
signal sng_go_duration_changed(duration: int)
|
||||
signal sng_stop_duration_changed(duration: int)
|
||||
signal sng_required_goals_changed(goals: int)
|
||||
|
||||
# Tekton Doors settings signals
|
||||
signal doors_swap_time_changed(time: int)
|
||||
signal doors_refresh_time_changed(time: int)
|
||||
signal doors_required_goals_changed(goals: int)
|
||||
|
||||
# Room data structure
|
||||
var current_room: Dictionary = {}
|
||||
var players_in_room: Array = [] # [{id, name, is_ready}]
|
||||
@@ -40,6 +50,16 @@ signal enable_cycle_timer_changed(enabled: bool)
|
||||
var scarcity_mode: String = "Normal" # Normal, Aggressive, Chaos
|
||||
signal scarcity_mode_changed(mode: String)
|
||||
|
||||
# Stop N Go settings
|
||||
var sng_go_duration: int = 15
|
||||
var sng_stop_duration: int = 4
|
||||
var sng_required_goals: int = 8
|
||||
|
||||
# Tekton Doors settings
|
||||
var doors_swap_time: int = 15
|
||||
var doors_refresh_time: int = 25
|
||||
var doors_required_goals: int = 8
|
||||
|
||||
# Character and area selection
|
||||
var available_characters: Array[String] = ["Copper", "Dabro", "Gatot", "Pip", "Random"]
|
||||
var available_areas: Array[String] = []
|
||||
@@ -259,6 +279,68 @@ func sync_scarcity_mode(mode: String) -> void:
|
||||
func get_scarcity_mode() -> String:
|
||||
return scarcity_mode
|
||||
|
||||
# =============================================================================
|
||||
# Stop N Go Settings
|
||||
# =============================================================================
|
||||
|
||||
func set_sng_go_duration(duration: int) -> void:
|
||||
sng_go_duration = duration
|
||||
if is_host: rpc("sync_sng_go_duration", duration)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_sng_go_duration(duration: int) -> void:
|
||||
sng_go_duration = duration
|
||||
emit_signal("sng_go_duration_changed", duration)
|
||||
|
||||
func set_sng_stop_duration(duration: int) -> void:
|
||||
sng_stop_duration = duration
|
||||
if is_host: rpc("sync_sng_stop_duration", duration)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_sng_stop_duration(duration: int) -> void:
|
||||
sng_stop_duration = duration
|
||||
emit_signal("sng_stop_duration_changed", duration)
|
||||
|
||||
func set_sng_required_goals(goals: int) -> void:
|
||||
sng_required_goals = goals
|
||||
if is_host: rpc("sync_sng_required_goals", goals)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_sng_required_goals(goals: int) -> void:
|
||||
sng_required_goals = goals
|
||||
emit_signal("sng_required_goals_changed", goals)
|
||||
|
||||
# =============================================================================
|
||||
# Tekton Doors Settings
|
||||
# =============================================================================
|
||||
|
||||
func set_doors_swap_time(time: int) -> void:
|
||||
doors_swap_time = time
|
||||
if is_host: rpc("sync_doors_swap_time", time)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_doors_swap_time(time: int) -> void:
|
||||
doors_swap_time = time
|
||||
emit_signal("doors_swap_time_changed", time)
|
||||
|
||||
func set_doors_refresh_time(time: int) -> void:
|
||||
doors_refresh_time = time
|
||||
if is_host: rpc("sync_doors_refresh_time", time)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_doors_refresh_time(time: int) -> void:
|
||||
doors_refresh_time = time
|
||||
emit_signal("doors_refresh_time_changed", time)
|
||||
|
||||
func set_doors_required_goals(goals: int) -> void:
|
||||
doors_required_goals = goals
|
||||
if is_host: rpc("sync_doors_required_goals", goals)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_doors_required_goals(goals: int) -> void:
|
||||
doors_required_goals = goals
|
||||
emit_signal("doors_required_goals_changed", goals)
|
||||
|
||||
# =============================================================================
|
||||
# Character Selection
|
||||
# =============================================================================
|
||||
@@ -430,6 +512,13 @@ func start_game() -> void:
|
||||
rpc("sync_enable_cycle_timer", enable_cycle_timer)
|
||||
# Sync scarcity mode
|
||||
rpc("sync_scarcity_mode", scarcity_mode)
|
||||
# Sync game mode features
|
||||
rpc("sync_sng_go_duration", sng_go_duration)
|
||||
rpc("sync_sng_stop_duration", sng_stop_duration)
|
||||
rpc("sync_sng_required_goals", sng_required_goals)
|
||||
rpc("sync_doors_swap_time", doors_swap_time)
|
||||
rpc("sync_doors_refresh_time", doors_refresh_time)
|
||||
rpc("sync_doors_required_goals", doors_required_goals)
|
||||
# Sync game mode
|
||||
rpc("sync_game_mode", game_mode)
|
||||
|
||||
@@ -492,6 +581,12 @@ func request_room_info(requester_id: int, requester_name: String, requester_char
|
||||
rpc_id(requester_id, "sync_randomize_spawn", randomize_spawn)
|
||||
rpc_id(requester_id, "sync_enable_cycle_timer", enable_cycle_timer)
|
||||
rpc_id(requester_id, "sync_scarcity_mode", scarcity_mode)
|
||||
rpc_id(requester_id, "sync_sng_go_duration", sng_go_duration)
|
||||
rpc_id(requester_id, "sync_sng_stop_duration", sng_stop_duration)
|
||||
rpc_id(requester_id, "sync_sng_required_goals", sng_required_goals)
|
||||
rpc_id(requester_id, "sync_doors_swap_time", doors_swap_time)
|
||||
rpc_id(requester_id, "sync_doors_refresh_time", doors_refresh_time)
|
||||
rpc_id(requester_id, "sync_doors_required_goals", doors_required_goals)
|
||||
rpc_id(requester_id, "sync_game_mode", game_mode)
|
||||
rpc_id(requester_id, "sync_area", selected_area)
|
||||
|
||||
@@ -568,3 +663,9 @@ func reset() -> void:
|
||||
selected_area = available_areas[0]
|
||||
local_character_index = 0 # Default to "Copper"
|
||||
enable_cycle_timer = false
|
||||
sng_go_duration = 15
|
||||
sng_stop_duration = 4
|
||||
sng_required_goals = 8
|
||||
doors_swap_time = 15
|
||||
doors_refresh_time = 25
|
||||
doors_required_goals = 8
|
||||
|
||||
@@ -17,7 +17,6 @@ var doors = [] # List of PortalDoor nodes
|
||||
var swap_timer: Timer
|
||||
var tile_refresh_timer: Timer
|
||||
var finish_spawned: bool = false
|
||||
var missions_required: int = 8
|
||||
var arena_setup_done: bool = false
|
||||
var player_portal_cooldowns: Dictionary = {}
|
||||
|
||||
@@ -41,6 +40,7 @@ func initialize(p_main: Node, p_gridmap: Node):
|
||||
# Connection Swap Timer (15s)
|
||||
swap_timer = Timer.new()
|
||||
swap_timer.name = "PortalSwapTimer"
|
||||
# Initial wait time; gets reset when started based on game mode settings
|
||||
swap_timer.wait_time = 15.0
|
||||
swap_timer.timeout.connect(_on_swap_timer_timeout)
|
||||
add_child(swap_timer)
|
||||
@@ -48,6 +48,7 @@ func initialize(p_main: Node, p_gridmap: Node):
|
||||
# Tile Refresh Timer (25s)
|
||||
tile_refresh_timer = Timer.new()
|
||||
tile_refresh_timer.name = "TileRefreshTimer"
|
||||
# Initial wait time; gets reset when started based on game mode settings
|
||||
tile_refresh_timer.wait_time = 25.0
|
||||
tile_refresh_timer.timeout.connect(_on_tile_refresh_timer_timeout)
|
||||
add_child(tile_refresh_timer)
|
||||
@@ -77,6 +78,10 @@ func start_game_mode():
|
||||
setup_arena_locally()
|
||||
_randomize_connections()
|
||||
|
||||
# Configure dynamic timings from LobbyManager before starting
|
||||
swap_timer.wait_time = float(LobbyManager.doors_swap_time)
|
||||
tile_refresh_timer.wait_time = float(LobbyManager.doors_refresh_time)
|
||||
|
||||
# Start Timers
|
||||
if swap_timer.is_stopped():
|
||||
swap_timer.start()
|
||||
@@ -176,9 +181,9 @@ func _update_hud_visuals():
|
||||
var gcm = main.get_node_or_null("GoalsCycleManager")
|
||||
var completed_count = gcm.player_goal_counts.get(my_id, 0) if gcm else 0
|
||||
|
||||
mission_label.text = "GOALS (%d/%d)" % [completed_count, missions_required]
|
||||
mission_label.text = "GOALS (%d/%d)" % [completed_count, LobbyManager.doors_required_goals]
|
||||
|
||||
if completed_count >= missions_required:
|
||||
if completed_count >= LobbyManager.doors_required_goals:
|
||||
mission_label.text = "ALL GOALS COMPLETE!\nFIND THE FINISH ROOM!"
|
||||
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
||||
|
||||
@@ -194,7 +199,7 @@ func _update_hud_visuals():
|
||||
func is_mission_complete(peer_id: int) -> bool:
|
||||
var gcm = main.get_node_or_null("GoalsCycleManager")
|
||||
if not gcm: return false
|
||||
return gcm.player_goal_counts.get(peer_id, 0) >= missions_required
|
||||
return gcm.player_goal_counts.get(peer_id, 0) >= LobbyManager.doors_required_goals
|
||||
|
||||
func check_win_condition(player_id: int, pos: Vector2i) -> bool:
|
||||
# 1. Check if on finish tile
|
||||
|
||||
@@ -9,10 +9,6 @@ signal player_penalized(player_id: int)
|
||||
|
||||
enum Phase {GO, STOP}
|
||||
|
||||
const GO_DURATION: float = 15.0
|
||||
const STOP_DURATION: float = 4.0
|
||||
const REQUIRED_GOALS: int = 8
|
||||
|
||||
# Dynamic Safe Zone
|
||||
const SAFE_ZONE_PRE_TIME: float = 5.0 # Seconds before STOP to spawn safe zone
|
||||
const SAFE_ZONE_RADIUS: int = 2 # 5x5 area (radius 2 from center)
|
||||
@@ -34,7 +30,7 @@ const PERMANENT_POWERUP_LOCATIONS: Array[Vector2i] = [
|
||||
]
|
||||
|
||||
var current_phase: Phase = Phase.GO
|
||||
var phase_timer: float = GO_DURATION
|
||||
var phase_timer: float = 15.0 # Initialized dynamically later
|
||||
var is_active: bool = false
|
||||
|
||||
var player_missions: Dictionary = {} # player_id -> {target_tile: int, required: int, current: int}
|
||||
@@ -134,10 +130,11 @@ func _update_hud_visuals():
|
||||
|
||||
# Get count from GoalsCycleManager (Source of truth for PlayerBoardLabel)
|
||||
var completed_count = goals_cycle_manager.player_goal_counts.get(my_id, 0) if goals_cycle_manager else 0
|
||||
var required_goals = LobbyManager.sng_required_goals
|
||||
|
||||
mission_label.text = "GOALS (%d/%d)" % [completed_count, REQUIRED_GOALS]
|
||||
mission_label.text = "GOALS (%d/%d)" % [completed_count, required_goals]
|
||||
|
||||
if completed_count >= REQUIRED_GOALS:
|
||||
if completed_count >= required_goals:
|
||||
mission_label.text = "ALL GOALS COMPLETE!\nREACH THE FINISH!"
|
||||
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
||||
|
||||
@@ -235,7 +232,7 @@ func start_game_mode():
|
||||
|
||||
func _start_phase(phase: Phase):
|
||||
current_phase = phase
|
||||
phase_timer = GO_DURATION if phase == Phase.GO else STOP_DURATION
|
||||
phase_timer = float(LobbyManager.sng_go_duration) if phase == Phase.GO else float(LobbyManager.sng_stop_duration)
|
||||
|
||||
var phase_name = "GO" if phase == Phase.GO else "STOP"
|
||||
if can_rpc():
|
||||
@@ -436,7 +433,7 @@ func is_mission_complete(player_id: int) -> bool:
|
||||
if not goals_cycle_manager: return false
|
||||
|
||||
var completed_count = goals_cycle_manager.player_goal_counts.get(player_id, 0)
|
||||
return completed_count >= REQUIRED_GOALS
|
||||
return completed_count >= LobbyManager.sng_required_goals
|
||||
|
||||
func check_win_condition(player_id: int, position: Vector2i) -> bool:
|
||||
# 1. Must reach the finish line (Column 21)
|
||||
@@ -452,7 +449,7 @@ func check_win_condition(player_id: int, position: Vector2i) -> bool:
|
||||
var main = get_node_or_null("/root/Main")
|
||||
var player_node = main.get_node_or_null(str(player_id)) if main else null
|
||||
if player_node:
|
||||
NotificationManager.send_message(player_node, "Incomplete! Achieve %d goals to win!" % REQUIRED_GOALS, NotificationManager.MessageType.WARNING)
|
||||
NotificationManager.send_message(player_node, "Incomplete! Achieve %d goals to win!" % LobbyManager.sng_required_goals, NotificationManager.MessageType.WARNING)
|
||||
|
||||
print("[StopNGo] Player %d reached finish but goals incomplete." % player_id)
|
||||
return false
|
||||
|
||||
@@ -16,12 +16,10 @@ const STATS_COLLECTION := "stats"
|
||||
|
||||
# Available avatars (predefined)
|
||||
const AVATARS := [
|
||||
"res://assets/avatars/avatar_default.png",
|
||||
"res://assets/avatars/avatar_warrior.png",
|
||||
"res://assets/avatars/avatar_mage.png",
|
||||
"res://assets/avatars/avatar_rogue.png",
|
||||
"res://assets/avatars/avatar_tank.png",
|
||||
"res://assets/avatars/avatar_healer.png",
|
||||
"res://assets/graphics/character_selection/sc_characters/sc_pip.png",
|
||||
"res://assets/graphics/character_selection/sc_characters/sc_gatot.png",
|
||||
"res://assets/graphics/character_selection/sc_characters/sc_dabro.png",
|
||||
"res://assets/graphics/character_selection/sc_characters/sc_copper.png"
|
||||
]
|
||||
|
||||
func _ready() -> void:
|
||||
|
||||
Reference in New Issue
Block a user