feat: Introduce a comprehensive game lobby system including UI, room management, and player interactions.
This commit is contained in:
@@ -9,7 +9,6 @@
|
|||||||
[ext_resource type="ArrayMesh" uid="uid://cv4bedhida00g" path="res://assets/models/tiles/tile_star.tres" id="7_p5epg"]
|
[ext_resource type="ArrayMesh" uid="uid://cv4bedhida00g" path="res://assets/models/tiles/tile_star.tres" id="7_p5epg"]
|
||||||
[ext_resource type="ArrayMesh" uid="uid://gpnl4cjrivor" path="res://assets/models/tiles/tile_speed.tres" id="7_sx8rm"]
|
[ext_resource type="ArrayMesh" uid="uid://gpnl4cjrivor" path="res://assets/models/tiles/tile_speed.tres" id="7_sx8rm"]
|
||||||
[ext_resource type="ArrayMesh" uid="uid://bfv8cw1vho5p5" path="res://assets/models/meshes/ancient_lightning_stones.res" id="8_cg50n"]
|
[ext_resource type="ArrayMesh" uid="uid://bfv8cw1vho5p5" path="res://assets/models/meshes/ancient_lightning_stones.res" id="8_cg50n"]
|
||||||
[ext_resource type="BoxMesh" uid="uid://fy4bhoeii40c" path="res://addons/enhanced_gridmap/meshlibrary/tile_safe_zone.tres" id="8_uwjsj"]
|
|
||||||
[ext_resource type="BoxMesh" uid="uid://dy5p77cjb3geo" path="res://addons/enhanced_gridmap/meshlibrary/tile_start.tres" id="9_pgnbl"]
|
[ext_resource type="BoxMesh" uid="uid://dy5p77cjb3geo" path="res://addons/enhanced_gridmap/meshlibrary/tile_start.tres" id="9_pgnbl"]
|
||||||
[ext_resource type="ArrayMesh" uid="uid://dtr46jmckif0p" path="res://assets/models/meshes/block.res" id="9_uwjsj"]
|
[ext_resource type="ArrayMesh" uid="uid://dtr46jmckif0p" path="res://assets/models/meshes/block.res" id="9_uwjsj"]
|
||||||
[ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"]
|
[ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"]
|
||||||
@@ -81,7 +80,6 @@ item/1/shapes = []
|
|||||||
item/1/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
item/1/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||||
item/1/navigation_layers = 1
|
item/1/navigation_layers = 1
|
||||||
item/2/name = "safe_zone"
|
item/2/name = "safe_zone"
|
||||||
item/2/mesh = ExtResource("8_uwjsj")
|
|
||||||
item/2/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
item/2/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||||
item/2/mesh_cast_shadow = 1
|
item/2/mesh_cast_shadow = 1
|
||||||
item/2/shapes = []
|
item/2/shapes = []
|
||||||
|
|||||||
+60
-93
@@ -2,7 +2,6 @@ extends Control
|
|||||||
|
|
||||||
# UI References - Main Menu
|
# UI References - Main Menu
|
||||||
@onready var main_menu_panel = $MainMenuPanel
|
@onready var main_menu_panel = $MainMenuPanel
|
||||||
@onready var player_name_input = $MainMenuPanel/VBoxContainer/InputSection/PlayerNameInput
|
|
||||||
@onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn
|
@onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn
|
||||||
@onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn
|
@onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn
|
||||||
@onready var main_menu_profile_btn = $MainMenuPanel/VBoxContainer/ButtonSection/ProfileBtn
|
@onready var main_menu_profile_btn = $MainMenuPanel/VBoxContainer/ButtonSection/ProfileBtn
|
||||||
@@ -12,10 +11,6 @@ extends Control
|
|||||||
# UI References - Server Selection
|
# UI References - Server Selection
|
||||||
@onready var server_option = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerOption
|
@onready var server_option = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerOption
|
||||||
@onready var server_ip_input = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerIPInput
|
@onready var server_ip_input = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerIPInput
|
||||||
@onready var lan_section = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection
|
|
||||||
@onready var lan_ip_input = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection/LANIPInput
|
|
||||||
@onready var lan_host_btn = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection/LANHostBtn
|
|
||||||
@onready var lan_join_btn = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection/LANJoinBtn
|
|
||||||
|
|
||||||
# Leaderboard Reference
|
# Leaderboard Reference
|
||||||
@onready var leaderboard_btn = $MainMenuPanel/VBoxContainer/ButtonSection/LeaderboardBtn
|
@onready var leaderboard_btn = $MainMenuPanel/VBoxContainer/ButtonSection/LeaderboardBtn
|
||||||
@@ -119,24 +114,11 @@ func _ready():
|
|||||||
# Setup Game Mode specific UI dynamically
|
# Setup Game Mode specific UI dynamically
|
||||||
_create_custom_settings_ui()
|
_create_custom_settings_ui()
|
||||||
|
|
||||||
# Set player name from profile and configure input visibility
|
# Set player name from profile
|
||||||
if player_name_input:
|
if AuthManager.is_guest:
|
||||||
# Get the parent container for the input to hide/show properly
|
LobbyManager.local_player_name = "Guest"
|
||||||
var input_section = player_name_input.get_parent()
|
else:
|
||||||
|
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
||||||
if AuthManager.is_guest:
|
|
||||||
# Guest user - show name input and let them enter a name
|
|
||||||
if input_section:
|
|
||||||
input_section.visible = true
|
|
||||||
player_name_input.text = "Guest"
|
|
||||||
player_name_input.editable = true
|
|
||||||
else:
|
|
||||||
# Logged-in user - hide name input and use profile name automatically
|
|
||||||
if input_section:
|
|
||||||
input_section.visible = false
|
|
||||||
player_name_input.text = UserProfileManager.get_display_name()
|
|
||||||
# Also set the LobbyManager name immediately
|
|
||||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
|
||||||
|
|
||||||
# Connect button signals - Main Menu
|
# Connect button signals - Main Menu
|
||||||
create_room_btn.pressed.connect(_on_create_room_pressed)
|
create_room_btn.pressed.connect(_on_create_room_pressed)
|
||||||
@@ -158,10 +140,6 @@ func _ready():
|
|||||||
if server_ip_input:
|
if server_ip_input:
|
||||||
server_ip_input.text_submitted.connect(_on_server_ip_submitted)
|
server_ip_input.text_submitted.connect(_on_server_ip_submitted)
|
||||||
server_ip_input.focus_exited.connect(func(): _on_server_ip_submitted(server_ip_input.text))
|
server_ip_input.focus_exited.connect(func(): _on_server_ip_submitted(server_ip_input.text))
|
||||||
if lan_host_btn:
|
|
||||||
lan_host_btn.pressed.connect(_on_lan_host_pressed)
|
|
||||||
if lan_join_btn:
|
|
||||||
lan_join_btn.pressed.connect(func(): _on_lan_join_pressed(lan_ip_input.text if lan_ip_input else "127.0.0.1"))
|
|
||||||
|
|
||||||
# Connect button signals - Room List
|
# Connect button signals - Room List
|
||||||
refresh_btn.pressed.connect(_on_refresh_pressed)
|
refresh_btn.pressed.connect(_on_refresh_pressed)
|
||||||
@@ -258,56 +236,22 @@ func _on_server_option_selected(index: int) -> void:
|
|||||||
if index == 0:
|
if index == 0:
|
||||||
# Nakama Localhost
|
# Nakama Localhost
|
||||||
if server_ip_input: server_ip_input.visible = false
|
if server_ip_input: server_ip_input.visible = false
|
||||||
if lan_section: lan_section.visible = false
|
|
||||||
NakamaManager.set_server("localhost")
|
NakamaManager.set_server("localhost")
|
||||||
|
LobbyManager.is_lan_mode = false
|
||||||
elif index == 1:
|
elif index == 1:
|
||||||
# Nakama Remote
|
# Nakama Remote
|
||||||
if server_ip_input: server_ip_input.visible = true
|
if server_ip_input: server_ip_input.visible = true
|
||||||
if lan_section: lan_section.visible = false
|
|
||||||
if server_ip_input: NakamaManager.set_server(server_ip_input.text)
|
if server_ip_input: NakamaManager.set_server(server_ip_input.text)
|
||||||
|
LobbyManager.is_lan_mode = false
|
||||||
else:
|
else:
|
||||||
# LAN Direct
|
# LAN Direct
|
||||||
if server_ip_input: server_ip_input.visible = false
|
if server_ip_input: server_ip_input.visible = false
|
||||||
if lan_section: lan_section.visible = true
|
LobbyManager.is_lan_mode = true
|
||||||
|
|
||||||
func _on_server_ip_submitted(new_text: String) -> void:
|
func _on_server_ip_submitted(new_text: String) -> void:
|
||||||
if server_option and server_option.selected == 1:
|
if server_option and server_option.selected == 1:
|
||||||
NakamaManager.set_server(new_text.strip_edges())
|
NakamaManager.set_server(new_text.strip_edges())
|
||||||
|
|
||||||
func _on_lan_host_pressed() -> void:
|
|
||||||
"""Host a LAN game without Nakama."""
|
|
||||||
var player_name = player_name_input.text.strip_edges() if player_name_input else ""
|
|
||||||
if player_name.is_empty():
|
|
||||||
player_name = "Host"
|
|
||||||
LobbyManager.local_player_name = player_name
|
|
||||||
|
|
||||||
if connection_status:
|
|
||||||
connection_status.text = "Starting LAN server..."
|
|
||||||
var ok = await LobbyManager.create_room_lan()
|
|
||||||
if not ok:
|
|
||||||
if connection_status:
|
|
||||||
connection_status.text = "Failed to start LAN server. Check port 7777."
|
|
||||||
|
|
||||||
func _on_lan_join_pressed(host_ip: String) -> void:
|
|
||||||
"""Join a LAN game by entering the host's IP."""
|
|
||||||
var ip = host_ip.strip_edges()
|
|
||||||
if ip.is_empty():
|
|
||||||
if connection_status:
|
|
||||||
connection_status.text = "Enter the host's IP address."
|
|
||||||
return
|
|
||||||
|
|
||||||
var player_name = player_name_input.text.strip_edges() if player_name_input else ""
|
|
||||||
if player_name.is_empty():
|
|
||||||
player_name = "Player"
|
|
||||||
LobbyManager.local_player_name = player_name
|
|
||||||
|
|
||||||
if connection_status:
|
|
||||||
connection_status.text = "Connecting to %s..." % ip
|
|
||||||
var ok = LobbyManager.join_room_lan(ip)
|
|
||||||
if not ok:
|
|
||||||
if connection_status:
|
|
||||||
connection_status.text = "Failed to connect to %s. Is host running?" % ip
|
|
||||||
|
|
||||||
func _setup_game_modes() -> void:
|
func _setup_game_modes() -> void:
|
||||||
if not game_mode_option: return
|
if not game_mode_option: return
|
||||||
game_mode_option.clear()
|
game_mode_option.clear()
|
||||||
@@ -362,21 +306,35 @@ func _show_panel(panel_name: String) -> void:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
func _on_create_room_pressed() -> void:
|
func _on_create_room_pressed() -> void:
|
||||||
# Use profile name for logged-in users, or input name for guests
|
# Use profile name for logged-in users, or guest for others
|
||||||
if AuthManager.is_guest:
|
if AuthManager.is_guest:
|
||||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
if LobbyManager.local_player_name.is_empty() or LobbyManager.local_player_name == "Player":
|
||||||
if LobbyManager.local_player_name.is_empty():
|
|
||||||
LobbyManager.local_player_name = "Guest"
|
LobbyManager.local_player_name = "Guest"
|
||||||
else:
|
else:
|
||||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
||||||
|
|
||||||
connection_status.text = "Creating room..."
|
if LobbyManager.is_lan_mode:
|
||||||
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
|
connection_status.text = "Starting LAN room..."
|
||||||
|
var ok = await LobbyManager.create_room_lan("LAN Room " + str(randi_range(100, 999)))
|
||||||
|
if not ok:
|
||||||
|
connection_status.text = "Failed to start LAN room. Check port 7777."
|
||||||
|
else:
|
||||||
|
connection_status.text = "Creating Nakama room..."
|
||||||
|
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
|
||||||
|
|
||||||
func _on_browse_rooms_pressed() -> void:
|
func _on_browse_rooms_pressed() -> void:
|
||||||
_show_panel("room_list")
|
_show_panel("room_list")
|
||||||
connection_status.text = "Loading rooms..."
|
|
||||||
LobbyManager.refresh_room_list()
|
if LobbyManager.is_lan_mode:
|
||||||
|
connection_status.text = "LAN Mode - Enter Host IP to join"
|
||||||
|
match_id_input.placeholder_text = "Enter Host IP (e.g. 192.168.1.10)..."
|
||||||
|
$RoomListPanel/VBoxContainer/MatchIdLabel.text = "DIRECT CONNECT (HOST IP)"
|
||||||
|
_on_refresh_pressed() # Try to discover rooms if implemented
|
||||||
|
else:
|
||||||
|
connection_status.text = "Loading Nakama rooms..."
|
||||||
|
match_id_input.placeholder_text = "Paste match ID here..."
|
||||||
|
$RoomListPanel/VBoxContainer/MatchIdLabel.text = "DIRECT CONNECT (MATCH ID)"
|
||||||
|
LobbyManager.refresh_room_list()
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Room List Button Handlers
|
# Room List Button Handlers
|
||||||
@@ -390,7 +348,7 @@ func _on_refresh_pressed() -> void:
|
|||||||
func _on_join_pressed() -> void:
|
func _on_join_pressed() -> void:
|
||||||
var match_id = match_id_input.text.strip_edges()
|
var match_id = match_id_input.text.strip_edges()
|
||||||
|
|
||||||
if match_id.is_empty():
|
if match_id.is_empty() and not LobbyManager.is_lan_mode:
|
||||||
var selected_items = room_list.get_selected_items()
|
var selected_items = room_list.get_selected_items()
|
||||||
if selected_items.size() == 0:
|
if selected_items.size() == 0:
|
||||||
connection_status.text = "Please select a room or enter Match ID"
|
connection_status.text = "Please select a room or enter Match ID"
|
||||||
@@ -400,20 +358,36 @@ func _on_join_pressed() -> void:
|
|||||||
if selected_idx < LobbyManager.available_rooms.size():
|
if selected_idx < LobbyManager.available_rooms.size():
|
||||||
match_id = LobbyManager.available_rooms[selected_idx].get("match_id", "")
|
match_id = LobbyManager.available_rooms[selected_idx].get("match_id", "")
|
||||||
|
|
||||||
if match_id.is_empty():
|
# Determine player name
|
||||||
connection_status.text = "No room selected"
|
|
||||||
return
|
|
||||||
|
|
||||||
# Use profile name for logged-in users, or input name for guests
|
|
||||||
if AuthManager.is_guest:
|
if AuthManager.is_guest:
|
||||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
|
||||||
if LobbyManager.local_player_name.is_empty():
|
if LobbyManager.local_player_name.is_empty():
|
||||||
LobbyManager.local_player_name = "Guest"
|
LobbyManager.local_player_name = "Guest"
|
||||||
else:
|
else:
|
||||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
||||||
|
|
||||||
connection_status.text = "Joining room..."
|
if LobbyManager.is_lan_mode:
|
||||||
LobbyManager.join_room(match_id)
|
if match_id.is_empty():
|
||||||
|
# If nothing entered but something selected in list (discovered), use it
|
||||||
|
var selected_items = room_list.get_selected_items()
|
||||||
|
if selected_items.size() > 0:
|
||||||
|
var idx = selected_items[0]
|
||||||
|
if idx < LobbyManager.available_rooms.size():
|
||||||
|
match_id = LobbyManager.available_rooms[idx].get("ip", "")
|
||||||
|
|
||||||
|
if match_id.is_empty():
|
||||||
|
connection_status.text = "Enter Host IP to join"
|
||||||
|
return
|
||||||
|
|
||||||
|
connection_status.text = "Connecting to %s..." % match_id
|
||||||
|
var ok = LobbyManager.join_room_lan(match_id)
|
||||||
|
if not ok:
|
||||||
|
connection_status.text = "Failed to connect to %s" % match_id
|
||||||
|
else:
|
||||||
|
if match_id.is_empty():
|
||||||
|
connection_status.text = "No room selected"
|
||||||
|
return
|
||||||
|
connection_status.text = "Joining Nakama room..."
|
||||||
|
LobbyManager.join_room(match_id)
|
||||||
|
|
||||||
func _on_back_pressed() -> void:
|
func _on_back_pressed() -> void:
|
||||||
_show_panel("main_menu")
|
_show_panel("main_menu")
|
||||||
@@ -729,13 +703,6 @@ func _on_room_joined(room_data: Dictionary) -> void:
|
|||||||
_update_player_slots()
|
_update_player_slots()
|
||||||
connection_status.text = "Connected to room"
|
connection_status.text = "Connected to room"
|
||||||
|
|
||||||
# LAN solo mode: host is auto-ready, enable Start Game immediately
|
|
||||||
if LobbyManager.is_lan_mode and is_host:
|
|
||||||
ready_btn.button_pressed = true
|
|
||||||
ready_btn.text = "READY ✓"
|
|
||||||
LobbyManager.force_solo_ready()
|
|
||||||
status_label.text = "LAN Solo — press Start Game when ready!"
|
|
||||||
|
|
||||||
func _on_room_left() -> void:
|
func _on_room_left() -> void:
|
||||||
_show_panel("main_menu")
|
_show_panel("main_menu")
|
||||||
connection_status.text = "Left room"
|
connection_status.text = "Left room"
|
||||||
@@ -758,8 +725,12 @@ func _on_ready_state_changed(_player_id: int, _is_ready: bool) -> void:
|
|||||||
|
|
||||||
func _on_all_players_ready() -> void:
|
func _on_all_players_ready() -> void:
|
||||||
if LobbyManager.is_host:
|
if LobbyManager.is_host:
|
||||||
start_game_btn.disabled = false
|
if LobbyManager.is_lan_mode and LobbyManager.players_in_room.size() == 1:
|
||||||
status_label.text = "All ready! Start the match!"
|
# Auto-start for solo LAN testing
|
||||||
|
LobbyManager.start_game()
|
||||||
|
else:
|
||||||
|
start_game_btn.disabled = false
|
||||||
|
status_label.text = "All ready! Start the match!"
|
||||||
else:
|
else:
|
||||||
status_label.text = "All ready! Waiting for host..."
|
status_label.text = "All ready! Waiting for host..."
|
||||||
|
|
||||||
@@ -801,10 +772,6 @@ func _on_profile_updated() -> void:
|
|||||||
"""Handle profile updates (name/avatar change)."""
|
"""Handle profile updates (name/avatar change)."""
|
||||||
var new_name = UserProfileManager.get_display_name()
|
var new_name = UserProfileManager.get_display_name()
|
||||||
|
|
||||||
# Update input if visible
|
|
||||||
if player_name_input:
|
|
||||||
player_name_input.text = new_name
|
|
||||||
|
|
||||||
# Sync to LobbyManager if we are in a room or just locally
|
# Sync to LobbyManager if we are in a room or just locally
|
||||||
LobbyManager.set_player_name(new_name)
|
LobbyManager.set_player_name(new_name)
|
||||||
|
|
||||||
|
|||||||
+12
-64
@@ -43,10 +43,10 @@ anchor_left = 0.5
|
|||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -220.0
|
offset_left = -221.0
|
||||||
offset_top = -240.0
|
offset_top = -320.0
|
||||||
offset_right = 220.0
|
offset_right = 219.0
|
||||||
offset_bottom = 240.0
|
offset_bottom = 286.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
|
|
||||||
@@ -76,22 +76,6 @@ horizontal_alignment = 1
|
|||||||
[node name="Separator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=126990892]
|
[node name="Separator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=126990892]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="InputSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=1865748579]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 10
|
|
||||||
|
|
||||||
[node name="PlayerNameLabel" type="Label" parent="MainMenuPanel/VBoxContainer/InputSection" unique_id=1017736748]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
|
|
||||||
theme_override_font_sizes/font_size = 13
|
|
||||||
text = "YOUR NAME"
|
|
||||||
|
|
||||||
[node name="PlayerNameInput" type="LineEdit" parent="MainMenuPanel/VBoxContainer/InputSection" unique_id=1668571796]
|
|
||||||
custom_minimum_size = Vector2(0, 44)
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Player"
|
|
||||||
placeholder_text = "Enter your name..."
|
|
||||||
|
|
||||||
[node name="ServerSelectionSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=748392101]
|
[node name="ServerSelectionSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=748392101]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 10
|
theme_override_constants/separation = 10
|
||||||
@@ -121,43 +105,6 @@ layout_mode = 2
|
|||||||
text = "127.0.0.1"
|
text = "127.0.0.1"
|
||||||
placeholder_text = "Enter Nakama Server IP..."
|
placeholder_text = "Enter Nakama Server IP..."
|
||||||
|
|
||||||
[node name="LANSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection" unique_id=748392110]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="LANInfo" type="Label" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392111]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_color = Color(0.7, 0.7, 0.7, 1)
|
|
||||||
theme_override_font_sizes/font_size = 12
|
|
||||||
text = "Play over LAN without any server.\nFirewall may need to allow port 7777."
|
|
||||||
autowrap_mode = 3
|
|
||||||
|
|
||||||
[node name="LANHostBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392112]
|
|
||||||
custom_minimum_size = Vector2(0, 44)
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_font_sizes/font_size = 14
|
|
||||||
text = "HOST LAN GAME"
|
|
||||||
|
|
||||||
[node name="LANOrLabel" type="Label" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392113]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_color = Color(0.5, 0.5, 0.5, 1)
|
|
||||||
theme_override_font_sizes/font_size = 11
|
|
||||||
text = "── or join a friend ──"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="LANIPInput" type="LineEdit" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392114]
|
|
||||||
custom_minimum_size = Vector2(0, 44)
|
|
||||||
layout_mode = 2
|
|
||||||
text = "127.0.0.1"
|
|
||||||
placeholder_text = "Host IP (e.g. 192.168.1.10)"
|
|
||||||
|
|
||||||
[node name="LANJoinBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392115]
|
|
||||||
custom_minimum_size = Vector2(0, 44)
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_font_sizes/font_size = 14
|
|
||||||
text = "JOIN LAN GAME"
|
|
||||||
|
|
||||||
[node name="ServerSeparator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=748392105]
|
[node name="ServerSeparator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=748392105]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
@@ -177,7 +124,7 @@ layout_mode = 2
|
|||||||
theme_override_font_sizes/font_size = 16
|
theme_override_font_sizes/font_size = 16
|
||||||
text = "BROWSE ROOMS"
|
text = "BROWSE ROOMS"
|
||||||
|
|
||||||
[node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection"]
|
[node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=216339260]
|
||||||
custom_minimum_size = Vector2(0, 48)
|
custom_minimum_size = Vector2(0, 48)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 16
|
theme_override_font_sizes/font_size = 16
|
||||||
@@ -190,14 +137,14 @@ theme_override_font_sizes/font_size = 16
|
|||||||
text = "SETTINGS"
|
text = "SETTINGS"
|
||||||
|
|
||||||
[node name="ProfileBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=1640960506]
|
[node name="ProfileBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=1640960506]
|
||||||
layout_mode = 2
|
|
||||||
custom_minimum_size = Vector2(0, 36)
|
custom_minimum_size = Vector2(0, 36)
|
||||||
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 14
|
||||||
text = "PROFILE"
|
text = "PROFILE"
|
||||||
|
|
||||||
[node name="QuitBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=123456780]
|
[node name="QuitBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=123456780]
|
||||||
layout_mode = 2
|
|
||||||
custom_minimum_size = Vector2(0, 36)
|
custom_minimum_size = Vector2(0, 36)
|
||||||
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 14
|
||||||
text = "QUIT GAME"
|
text = "QUIT GAME"
|
||||||
|
|
||||||
@@ -278,6 +225,7 @@ layout_mode = 2
|
|||||||
text = "PROFILE"
|
text = "PROFILE"
|
||||||
|
|
||||||
[node name="LobbyPanel" type="Control" parent="." unique_id=1745714811]
|
[node name="LobbyPanel" type="Control" parent="." unique_id=1745714811]
|
||||||
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -1188,10 +1136,10 @@ anchors_preset = 12
|
|||||||
anchor_top = 1.0
|
anchor_top = 1.0
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = 464.0
|
offset_left = 466.0
|
||||||
offset_top = -93.0
|
offset_top = -65.0
|
||||||
offset_right = -461.0
|
offset_right = -459.0
|
||||||
offset_bottom = -44.0
|
offset_bottom = -16.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 0
|
grow_vertical = 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
[gd_scene format=3 uid="uid://cggmcgvdj6wxt"]
|
[gd_scene format=3 uid="uid://cggmcgvdj6wxt"]
|
||||||
|
|
||||||
[ext_resource type="ArrayMesh" uid="uid://dtr46jmckif0p" path="res://assets/models/meshes/block.res" id="1_block"]
|
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_wall"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_wall"]
|
||||||
size = Vector3(1.68, 1.5, 0.05)
|
size = Vector3(1.68, 1.5, 0.05)
|
||||||
|
|
||||||
@@ -10,7 +8,6 @@ collision_mask = 0
|
|||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1405008923]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1405008923]
|
||||||
transform = Transform3D(1.68, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
transform = Transform3D(1.68, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||||
mesh = ExtResource("1_block")
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1446599023]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1446599023]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35764623, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35764623, 0)
|
||||||
|
|||||||
@@ -38,8 +38,12 @@ var available_rooms: Array = []
|
|||||||
var is_host: bool = false
|
var is_host: bool = false
|
||||||
var is_lan_mode: bool = false # True when using direct ENet (no Nakama)
|
var is_lan_mode: bool = false # True when using direct ENet (no Nakama)
|
||||||
const LAN_PORT: int = 7777 # Port for LAN direct connections
|
const LAN_PORT: int = 7777 # Port for LAN direct connections
|
||||||
|
const LAN_DISCOVERY_PORT: int = 7778 # Port for LAN discovery
|
||||||
var local_player_name: String = "Player"
|
var local_player_name: String = "Player"
|
||||||
|
|
||||||
|
var _udp_peer: PacketPeerUDP
|
||||||
|
var _broadcast_timer: Timer
|
||||||
|
|
||||||
# Match duration in seconds (configurable in lobby by host)
|
# Match duration in seconds (configurable in lobby by host)
|
||||||
var match_duration: int = 180 # Default 3 minutes
|
var match_duration: int = 180 # Default 3 minutes
|
||||||
|
|
||||||
@@ -93,12 +97,35 @@ var _all_ready: bool = false
|
|||||||
func _ready():
|
func _ready():
|
||||||
_update_available_areas(game_mode)
|
_update_available_areas(game_mode)
|
||||||
|
|
||||||
|
# Setup UDP for LAN discovery
|
||||||
|
_udp_peer = PacketPeerUDP.new()
|
||||||
|
|
||||||
# Connect to Nakama signals
|
# Connect to Nakama signals
|
||||||
NakamaManager.match_joined.connect(_on_match_joined)
|
NakamaManager.match_joined.connect(_on_match_joined)
|
||||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
_listen_for_lan_discovery()
|
||||||
|
|
||||||
|
func _listen_for_lan_discovery():
|
||||||
|
if not _udp_peer or not is_lan_mode or is_host: return
|
||||||
|
if not _udp_peer.is_bound(): return
|
||||||
|
|
||||||
|
while _udp_peer.get_available_packet_count() > 0:
|
||||||
|
var packet = _udp_peer.get_packet()
|
||||||
|
var ip = _udp_peer.get_packet_ip()
|
||||||
|
var data_str = packet.get_string_from_utf8()
|
||||||
|
|
||||||
|
if not data_str.begins_with("TEKTON_HOST:"): continue
|
||||||
|
|
||||||
|
var room_info_json = data_str.trim_prefix("TEKTON_HOST:")
|
||||||
|
var room_info = JSON.parse_string(room_info_json)
|
||||||
|
if room_info is Dictionary:
|
||||||
|
room_info["ip"] = ip
|
||||||
|
_update_lan_room_list(room_info)
|
||||||
|
|
||||||
func _update_available_areas(mode: String) -> void:
|
func _update_available_areas(mode: String) -> void:
|
||||||
match mode:
|
match mode:
|
||||||
"Freemode":
|
"Freemode":
|
||||||
@@ -179,9 +206,55 @@ func create_room_lan(room_name: String = "LAN Game") -> bool:
|
|||||||
})
|
})
|
||||||
|
|
||||||
print("[LAN] Server created on port %d. Waiting for players..." % LAN_PORT)
|
print("[LAN] Server created on port %d. Waiting for players..." % LAN_PORT)
|
||||||
|
_start_lan_broadcast(room_name)
|
||||||
emit_signal("room_joined", current_room)
|
emit_signal("room_joined", current_room)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
func _start_lan_broadcast(room_name: String):
|
||||||
|
_stop_lan_broadcast()
|
||||||
|
if _udp_peer.bind(0) != OK:
|
||||||
|
push_error("[LAN] Failed to bind UDP for broadcasting")
|
||||||
|
return
|
||||||
|
_udp_peer.set_broadcast_enabled(true)
|
||||||
|
|
||||||
|
_broadcast_timer = Timer.new()
|
||||||
|
_broadcast_timer.name = "LANBroadcastTimer"
|
||||||
|
_broadcast_timer.wait_time = 2.0
|
||||||
|
_broadcast_timer.autostart = true
|
||||||
|
add_child(_broadcast_timer)
|
||||||
|
_broadcast_timer.timeout.connect(_broadcast_lan_room.bind(room_name))
|
||||||
|
|
||||||
|
func _broadcast_lan_room(room_name: String):
|
||||||
|
var room_data = {
|
||||||
|
"room_name": room_name,
|
||||||
|
"host_name": local_player_name,
|
||||||
|
"player_count": players_in_room.size(),
|
||||||
|
"max_players": GameStateManager.max_players,
|
||||||
|
"match_id": "LAN"
|
||||||
|
}
|
||||||
|
var msg = "TEKTON_HOST:" + JSON.stringify(room_data)
|
||||||
|
_udp_peer.set_dest_address("255.255.255.255", LAN_DISCOVERY_PORT)
|
||||||
|
_udp_peer.put_packet(msg.to_utf8_buffer())
|
||||||
|
|
||||||
|
func _stop_lan_broadcast():
|
||||||
|
if _broadcast_timer:
|
||||||
|
_broadcast_timer.stop()
|
||||||
|
_broadcast_timer.queue_free()
|
||||||
|
_broadcast_timer = null
|
||||||
|
if _udp_peer and _udp_peer.is_bound():
|
||||||
|
_udp_peer.close()
|
||||||
|
|
||||||
|
func _update_lan_room_list(room_info: Dictionary):
|
||||||
|
var found = false
|
||||||
|
for i in range(available_rooms.size()):
|
||||||
|
if available_rooms[i].get("ip") == room_info["ip"]:
|
||||||
|
available_rooms[i] = room_info
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
available_rooms.append(room_info)
|
||||||
|
emit_signal("room_list_updated", available_rooms)
|
||||||
|
|
||||||
func join_room_lan(host_ip: String) -> bool:
|
func join_room_lan(host_ip: String) -> bool:
|
||||||
"""Client joins a LAN room by the host's IP address. No Nakama/Docker required."""
|
"""Client joins a LAN room by the host's IP address. No Nakama/Docker required."""
|
||||||
is_host = false
|
is_host = false
|
||||||
@@ -215,6 +288,7 @@ func leave_room() -> void:
|
|||||||
|
|
||||||
# Important: Reset all lobby settings and player lists first
|
# Important: Reset all lobby settings and player lists first
|
||||||
reset()
|
reset()
|
||||||
|
_stop_lan_broadcast()
|
||||||
|
|
||||||
if is_lan_mode:
|
if is_lan_mode:
|
||||||
# LAN mode: just close the ENet peer directly
|
# LAN mode: just close the ENet peer directly
|
||||||
@@ -231,7 +305,16 @@ func leave_room() -> void:
|
|||||||
emit_signal("room_left")
|
emit_signal("room_left")
|
||||||
|
|
||||||
func refresh_room_list() -> void:
|
func refresh_room_list() -> void:
|
||||||
"""Request updated room list from Nakama."""
|
"""Request updated room list from Nakama or scan for LAN rooms."""
|
||||||
|
if is_lan_mode:
|
||||||
|
available_rooms.clear()
|
||||||
|
if _udp_peer.is_bound(): _udp_peer.close()
|
||||||
|
var err = _udp_peer.bind(LAN_DISCOVERY_PORT)
|
||||||
|
if err != OK:
|
||||||
|
push_error("[LAN] Failed to bind to discovery port %d" % LAN_DISCOVERY_PORT)
|
||||||
|
emit_signal("room_list_updated", available_rooms)
|
||||||
|
return
|
||||||
|
|
||||||
if not NakamaManager.is_connected_to_nakama():
|
if not NakamaManager.is_connected_to_nakama():
|
||||||
var success = await NakamaManager.connect_to_nakama_async()
|
var success = await NakamaManager.connect_to_nakama_async()
|
||||||
if not success:
|
if not success:
|
||||||
|
|||||||
Reference in New Issue
Block a user