add a chat window
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
A Godot 4.6 multiplayer cooperative game supporting both local and online multiplayer with physics-based interactions.
|
A Godot 4.6 multiplayer cooperative game supporting both local and online multiplayer with physics-based interactions.
|
||||||
|
|
||||||
|
Start with arguments:
|
||||||
|
--host or --join # instantly hosts or joins a game
|
||||||
|
--room-debug # shows information above each room what types of puzzles it has, and if player entered the room or not...
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Multiplayer Support
|
### Multiplayer Support
|
||||||
|
|||||||
66
src/scenes/chat_ui.tscn
Normal file
66
src/scenes/chat_ui.tscn
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
[gd_scene format=3 uid="uid://chatui1234567"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/chat_ui.gd" id="1_chat_ui"]
|
||||||
|
[ext_resource type="FontFile" path="res://assets/fonts/Metropolis/TrueType/Metropolis-Regular.ttf" id="2_metropolis"]
|
||||||
|
|
||||||
|
[sub_resource type="Theme" id="Theme_metropolis"]
|
||||||
|
default_font = ExtResource("2_metropolis")
|
||||||
|
default_font_size = 12
|
||||||
|
|
||||||
|
[node name="ChatUI" type="CanvasLayer" unique_id=2000000000]
|
||||||
|
layer = 200
|
||||||
|
script = ExtResource("1_chat_ui")
|
||||||
|
|
||||||
|
[node name="ChatContainer" type="Control" parent="." unique_id=3000000000]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 1
|
||||||
|
|
||||||
|
[node name="BottomLeft" type="MarginContainer" parent="ChatContainer" unique_id=4000000000]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 0.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 0.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = -200.0
|
||||||
|
offset_right = 400.0
|
||||||
|
offset_bottom = -10.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
|
||||||
|
[node name="Background" type="ColorRect" parent="ChatContainer/BottomLeft" unique_id=5000000000]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0, 0, 0, 0.5)
|
||||||
|
mouse_filter = 1
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="VBox" type="VBoxContainer" parent="ChatContainer/BottomLeft" unique_id=6000000000]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="MessageScroll" type="ScrollContainer" parent="ChatContainer/BottomLeft/VBox" unique_id=7000000000]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
custom_minimum_size = Vector2(380, 150)
|
||||||
|
|
||||||
|
[node name="MessageList" type="VBoxContainer" parent="ChatContainer/BottomLeft/VBox/MessageScroll" unique_id=8000000000]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="ChatInput" type="LineEdit" parent="ChatContainer/BottomLeft/VBox" unique_id=10000000000]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme = SubResource("Theme_metropolis")
|
||||||
|
placeholder_text = "Type a message..."
|
||||||
|
visible = false
|
||||||
353
src/scripts/chat_ui.gd
Normal file
353
src/scripts/chat_ui.gd
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
# Chat UI - Handles in-game chat with network sync
|
||||||
|
|
||||||
|
const MAX_MESSAGES = 100 # Maximum messages to keep in history
|
||||||
|
const MESSAGE_FADE_TIME = 5.0 # Seconds before message starts fading
|
||||||
|
const MESSAGE_FADE_DURATION = 2.0 # Duration of fade out
|
||||||
|
const RECENT_MESSAGE_TIME = 7.0 # Show background if message is this recent
|
||||||
|
|
||||||
|
# Player colors for chat names
|
||||||
|
var player_colors = [
|
||||||
|
Color.RED,
|
||||||
|
Color.GREEN,
|
||||||
|
Color.BLUE,
|
||||||
|
Color.ORANGE,
|
||||||
|
Color(0.5, 0.0, 0.5) # Purple
|
||||||
|
]
|
||||||
|
|
||||||
|
var chat_open: bool = false
|
||||||
|
var messages: Array = [] # Array of {timestamp: String, player_name: String, message: String, time: float}
|
||||||
|
var network_manager: Node = null
|
||||||
|
|
||||||
|
# UI Nodes
|
||||||
|
var chat_container: Control = null
|
||||||
|
var message_scroll: ScrollContainer = null
|
||||||
|
var message_list: VBoxContainer = null
|
||||||
|
var chat_input: LineEdit = null
|
||||||
|
var background: ColorRect = null
|
||||||
|
|
||||||
|
# Metropolis font
|
||||||
|
var metropolis_font: FontFile = null
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
network_manager = get_node_or_null("/root/NetworkManager")
|
||||||
|
|
||||||
|
# Load Metropolis font
|
||||||
|
var font_path = "res://assets/fonts/Metropolis/TrueType/Metropolis-Regular.ttf"
|
||||||
|
if ResourceLoader.exists(font_path):
|
||||||
|
metropolis_font = load(font_path)
|
||||||
|
|
||||||
|
# Get UI nodes from scene
|
||||||
|
chat_container = get_node_or_null("ChatContainer")
|
||||||
|
background = get_node_or_null("ChatContainer/BottomLeft/Background")
|
||||||
|
message_scroll = get_node_or_null("ChatContainer/BottomLeft/VBox/MessageScroll")
|
||||||
|
message_list = get_node_or_null("ChatContainer/BottomLeft/VBox/MessageScroll/MessageList")
|
||||||
|
chat_input = get_node_or_null("ChatContainer/BottomLeft/VBox/ChatInput")
|
||||||
|
|
||||||
|
# Apply Metropolis font to chat input if available
|
||||||
|
if chat_input and metropolis_font:
|
||||||
|
chat_input.add_theme_font_override("font", metropolis_font)
|
||||||
|
chat_input.add_theme_font_size_override("font_size", 14)
|
||||||
|
|
||||||
|
# Connect input signals
|
||||||
|
if chat_input:
|
||||||
|
chat_input.text_submitted.connect(_on_chat_input_submitted)
|
||||||
|
chat_input.focus_exited.connect(_on_chat_input_focus_exited)
|
||||||
|
|
||||||
|
visible = true # Chat UI is always visible (background and messages fade)
|
||||||
|
|
||||||
|
# Hide scrollbar by default (will show when chat is opened)
|
||||||
|
if message_scroll:
|
||||||
|
var scroll_bar = message_scroll.get_v_scroll_bar()
|
||||||
|
if scroll_bar:
|
||||||
|
scroll_bar.visible = false
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
# Toggle chat with ENTER key
|
||||||
|
if event is InputEventKey and event.pressed and event.keycode == KEY_ENTER:
|
||||||
|
if not chat_open:
|
||||||
|
_open_chat()
|
||||||
|
else:
|
||||||
|
_send_and_close_chat()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
func _open_chat():
|
||||||
|
chat_open = true
|
||||||
|
chat_input.visible = true
|
||||||
|
chat_input.text = ""
|
||||||
|
chat_input.grab_focus()
|
||||||
|
background.visible = true
|
||||||
|
|
||||||
|
# Show scrollbar when chat is open
|
||||||
|
if message_scroll:
|
||||||
|
var scroll_bar = message_scroll.get_v_scroll_bar()
|
||||||
|
if scroll_bar:
|
||||||
|
scroll_bar.visible = true
|
||||||
|
|
||||||
|
# Make all messages fully visible when chat is opened
|
||||||
|
_show_all_messages()
|
||||||
|
|
||||||
|
# Lock player controls
|
||||||
|
_lock_player_controls(true)
|
||||||
|
|
||||||
|
# Scroll to bottom
|
||||||
|
call_deferred("_scroll_to_bottom")
|
||||||
|
|
||||||
|
func _send_and_close_chat():
|
||||||
|
var message_text = chat_input.text.strip_edges()
|
||||||
|
chat_input.text = ""
|
||||||
|
chat_open = false
|
||||||
|
chat_input.visible = false
|
||||||
|
chat_input.release_focus()
|
||||||
|
|
||||||
|
# Hide scrollbar when chat is closed
|
||||||
|
if message_scroll:
|
||||||
|
var scroll_bar = message_scroll.get_v_scroll_bar()
|
||||||
|
if scroll_bar:
|
||||||
|
scroll_bar.visible = false
|
||||||
|
|
||||||
|
# Unlock player controls
|
||||||
|
_lock_player_controls(false)
|
||||||
|
|
||||||
|
# Send message if not empty
|
||||||
|
if message_text.length() > 0:
|
||||||
|
_send_message(message_text)
|
||||||
|
|
||||||
|
# Update background visibility
|
||||||
|
_update_background_visibility()
|
||||||
|
|
||||||
|
func _on_chat_input_submitted(text: String):
|
||||||
|
_send_and_close_chat()
|
||||||
|
|
||||||
|
func _on_chat_input_focus_exited():
|
||||||
|
# Don't close chat on focus loss - only close when ENTER is pressed
|
||||||
|
pass
|
||||||
|
|
||||||
|
func send_system_message(message: String):
|
||||||
|
# Send a system message (appears as "System" instead of player name)
|
||||||
|
# System messages are always sent from server
|
||||||
|
if multiplayer.has_multiplayer_peer():
|
||||||
|
if multiplayer.is_server():
|
||||||
|
# Server broadcasts to all clients and also shows locally
|
||||||
|
_add_message("System", message) # Show locally first
|
||||||
|
_receive_message.rpc("System", message) # Broadcast to clients
|
||||||
|
else:
|
||||||
|
# Client sends to server (system messages should only come from server)
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Offline mode - just show locally
|
||||||
|
_add_message("System", message)
|
||||||
|
|
||||||
|
func _send_message(message: String):
|
||||||
|
if not network_manager:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get player name
|
||||||
|
var player_name = _get_player_name()
|
||||||
|
|
||||||
|
# Send to server (or broadcast if we're the server)
|
||||||
|
if multiplayer.has_multiplayer_peer():
|
||||||
|
if multiplayer.is_server():
|
||||||
|
# Server broadcasts to all clients and also shows locally
|
||||||
|
_add_message(player_name, message) # Show locally first
|
||||||
|
_receive_message.rpc(player_name, message) # Broadcast to clients
|
||||||
|
else:
|
||||||
|
# Client sends to server
|
||||||
|
_send_message_to_server.rpc_id(1, player_name, message)
|
||||||
|
else:
|
||||||
|
# Offline mode - just show locally
|
||||||
|
_add_message(player_name, message)
|
||||||
|
|
||||||
|
@rpc("any_peer", "reliable")
|
||||||
|
func _send_message_to_server(player_name: String, message: String):
|
||||||
|
# Server receives message from client and broadcasts to all
|
||||||
|
if multiplayer.is_server():
|
||||||
|
# Show message on server first
|
||||||
|
_add_message(player_name, message)
|
||||||
|
# Then broadcast to all clients
|
||||||
|
_receive_message.rpc(player_name, message)
|
||||||
|
|
||||||
|
@rpc("authority", "reliable")
|
||||||
|
func _receive_message(player_name: String, message: String):
|
||||||
|
# All clients (and server) receive the message
|
||||||
|
_add_message(player_name, message)
|
||||||
|
|
||||||
|
func _get_player_color(player_name: String) -> Color:
|
||||||
|
# System messages use gray color
|
||||||
|
if player_name == "System":
|
||||||
|
return Color.GRAY
|
||||||
|
|
||||||
|
# Extract peer_id from player name (format: "PlayerX_Y" or "PlayerX")
|
||||||
|
var peer_id = 0
|
||||||
|
if player_name.begins_with("Player"):
|
||||||
|
var parts = player_name.substr(6).split("_") # Remove "Player" prefix
|
||||||
|
if parts.size() > 0:
|
||||||
|
var peer_str = parts[0]
|
||||||
|
if peer_str.is_valid_int():
|
||||||
|
peer_id = peer_str.to_int()
|
||||||
|
|
||||||
|
# Use peer_id to consistently assign colors
|
||||||
|
return player_colors[peer_id % player_colors.size()]
|
||||||
|
|
||||||
|
func _add_message(player_name: String, message: String):
|
||||||
|
if not message_list:
|
||||||
|
print("ChatUI: ERROR - message_list is null, cannot add message!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get current time
|
||||||
|
var time_dict = Time.get_time_dict_from_system()
|
||||||
|
var timestamp = "%02d:%02d:%02d" % [time_dict.hour, time_dict.minute, time_dict.second]
|
||||||
|
var current_time = Time.get_ticks_msec() / 1000.0
|
||||||
|
|
||||||
|
# Add to messages array
|
||||||
|
messages.append({
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"player_name": player_name,
|
||||||
|
"message": message,
|
||||||
|
"time": current_time
|
||||||
|
})
|
||||||
|
|
||||||
|
# Limit message history
|
||||||
|
if messages.size() > MAX_MESSAGES:
|
||||||
|
messages.pop_front()
|
||||||
|
|
||||||
|
# Get player color
|
||||||
|
var player_color = _get_player_color(player_name)
|
||||||
|
var color_hex = "#%02x%02x%02x" % [int(player_color.r * 255), int(player_color.g * 255), int(player_color.b * 255)]
|
||||||
|
|
||||||
|
# Create message label using RichTextLabel for colored text
|
||||||
|
var message_label = RichTextLabel.new()
|
||||||
|
message_label.name = "Message_%d" % messages.size()
|
||||||
|
message_label.bbcode_enabled = true
|
||||||
|
message_label.scroll_active = false
|
||||||
|
message_label.fit_content = true
|
||||||
|
message_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
message_label.clip_contents = false
|
||||||
|
|
||||||
|
# Format message with colored player name using BBCode
|
||||||
|
var formatted_text = "%s [color=%s]%s[/color]: %s" % [timestamp, color_hex, player_name, message]
|
||||||
|
message_label.text = formatted_text
|
||||||
|
|
||||||
|
# Apply Metropolis font if available
|
||||||
|
if metropolis_font:
|
||||||
|
message_label.add_theme_font_override("normal_font", metropolis_font)
|
||||||
|
message_label.add_theme_font_size_override("normal_font_size", 12)
|
||||||
|
|
||||||
|
message_label.modulate.a = 1.0
|
||||||
|
|
||||||
|
# Add to message list (at the end, so newest messages appear closest to input)
|
||||||
|
# Messages flow upwards - oldest at top, newest at bottom
|
||||||
|
message_list.add_child(message_label)
|
||||||
|
|
||||||
|
# Scroll to bottom
|
||||||
|
call_deferred("_scroll_to_bottom")
|
||||||
|
|
||||||
|
# Update background visibility
|
||||||
|
_update_background_visibility()
|
||||||
|
|
||||||
|
func _scroll_to_bottom():
|
||||||
|
if message_scroll:
|
||||||
|
await get_tree().process_frame # Wait for layout to update
|
||||||
|
var scroll_bar = message_scroll.get_v_scroll_bar()
|
||||||
|
if scroll_bar:
|
||||||
|
message_scroll.scroll_vertical = int(scroll_bar.max_value)
|
||||||
|
|
||||||
|
func _update_background_visibility():
|
||||||
|
if not background:
|
||||||
|
return
|
||||||
|
|
||||||
|
var current_time = Time.get_ticks_msec() / 1000.0
|
||||||
|
var has_recent_message = false
|
||||||
|
|
||||||
|
# Check if there are any recent messages
|
||||||
|
for msg in messages:
|
||||||
|
var age = current_time - msg.time
|
||||||
|
if age < RECENT_MESSAGE_TIME:
|
||||||
|
has_recent_message = true
|
||||||
|
break
|
||||||
|
|
||||||
|
# Show background if chat is open OR if there are recent messages
|
||||||
|
background.visible = chat_open or has_recent_message
|
||||||
|
|
||||||
|
# Update message fade (only when chat is closed)
|
||||||
|
if not chat_open:
|
||||||
|
_update_message_fades()
|
||||||
|
|
||||||
|
func _show_all_messages():
|
||||||
|
# Make all messages fully visible (used when chat is opened)
|
||||||
|
if not message_list:
|
||||||
|
return
|
||||||
|
|
||||||
|
var message_labels = []
|
||||||
|
for child in message_list.get_children():
|
||||||
|
if child is RichTextLabel:
|
||||||
|
message_labels.append(child)
|
||||||
|
|
||||||
|
for label in message_labels:
|
||||||
|
label.modulate.a = 1.0
|
||||||
|
|
||||||
|
func _update_message_fades():
|
||||||
|
if not message_list:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Don't fade messages when chat is open - they should all be visible
|
||||||
|
if chat_open:
|
||||||
|
return
|
||||||
|
|
||||||
|
var current_time = Time.get_ticks_msec() / 1000.0
|
||||||
|
|
||||||
|
# Update fade for each message label
|
||||||
|
# Messages are in chronological order (oldest first, newest last)
|
||||||
|
var message_labels = []
|
||||||
|
for child in message_list.get_children():
|
||||||
|
if child is RichTextLabel:
|
||||||
|
message_labels.append(child)
|
||||||
|
|
||||||
|
# Match labels to messages (same order - oldest first, newest last)
|
||||||
|
for i in range(min(messages.size(), message_labels.size())):
|
||||||
|
var msg = messages[i]
|
||||||
|
var label = message_labels[i]
|
||||||
|
var age = current_time - msg.time
|
||||||
|
|
||||||
|
if age > MESSAGE_FADE_TIME:
|
||||||
|
# Start fading
|
||||||
|
var fade_progress = (age - MESSAGE_FADE_TIME) / MESSAGE_FADE_DURATION
|
||||||
|
label.modulate.a = max(0.0, 1.0 - fade_progress)
|
||||||
|
else:
|
||||||
|
# Fully visible
|
||||||
|
label.modulate.a = 1.0
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
# Update message fades and background visibility
|
||||||
|
if not chat_open:
|
||||||
|
_update_background_visibility()
|
||||||
|
_update_message_fades()
|
||||||
|
|
||||||
|
func _get_player_name() -> String:
|
||||||
|
# Get local player name
|
||||||
|
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||||
|
if game_world:
|
||||||
|
var player_manager = game_world.get_node_or_null("PlayerManager")
|
||||||
|
if player_manager:
|
||||||
|
var local_players = player_manager.get_local_players()
|
||||||
|
if local_players.size() > 0:
|
||||||
|
var player = local_players[0]
|
||||||
|
# peer_id and local_player_index are always defined in player.gd
|
||||||
|
return "Player%d_%d" % [player.peer_id, player.local_player_index + 1]
|
||||||
|
|
||||||
|
# Fallback
|
||||||
|
if multiplayer.has_multiplayer_peer():
|
||||||
|
return "Player%d" % multiplayer.get_unique_id()
|
||||||
|
return "Player"
|
||||||
|
|
||||||
|
func _lock_player_controls(lock: bool):
|
||||||
|
# Lock/unlock controls for all local players
|
||||||
|
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||||
|
if game_world:
|
||||||
|
var player_manager = game_world.get_node_or_null("PlayerManager")
|
||||||
|
if player_manager:
|
||||||
|
var local_players = player_manager.get_local_players()
|
||||||
|
for player in local_players:
|
||||||
|
# controls_disabled is a property in player.gd, so we can set it directly
|
||||||
|
player.controls_disabled = lock
|
||||||
1
src/scripts/chat_ui.gd.uid
Normal file
1
src/scripts/chat_ui.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dxglerf7e7p5j
|
||||||
@@ -36,6 +36,9 @@ func _ready():
|
|||||||
network_manager.player_connected.connect(_on_player_connected)
|
network_manager.player_connected.connect(_on_player_connected)
|
||||||
network_manager.player_disconnected.connect(_on_player_disconnected)
|
network_manager.player_disconnected.connect(_on_player_disconnected)
|
||||||
|
|
||||||
|
# Create chat UI
|
||||||
|
_create_chat_ui()
|
||||||
|
|
||||||
# Generate dungeon on host
|
# Generate dungeon on host
|
||||||
if multiplayer.is_server() or not multiplayer.has_multiplayer_peer():
|
if multiplayer.is_server() or not multiplayer.has_multiplayer_peer():
|
||||||
print("GameWorld: _ready() - Will generate dungeon (is_server: ", multiplayer.is_server(), ", has_peer: ", multiplayer.has_multiplayer_peer(), ")")
|
print("GameWorld: _ready() - Will generate dungeon (is_server: ", multiplayer.is_server(), ", has_peer: ", multiplayer.has_multiplayer_peer(), ")")
|
||||||
@@ -59,6 +62,10 @@ func _spawn_all_players():
|
|||||||
func _on_player_connected(peer_id: int, player_info: Dictionary):
|
func _on_player_connected(peer_id: int, player_info: Dictionary):
|
||||||
print("GameWorld: Player connected signal received for peer ", peer_id, " with info: ", player_info)
|
print("GameWorld: Player connected signal received for peer ", peer_id, " with info: ", player_info)
|
||||||
|
|
||||||
|
# Send join message to chat (only on server to avoid duplicates)
|
||||||
|
if multiplayer.is_server():
|
||||||
|
_send_player_join_message(peer_id, player_info)
|
||||||
|
|
||||||
# Reset ready status for this peer (they need to notify again after spawning)
|
# Reset ready status for this peer (they need to notify again after spawning)
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
clients_ready[peer_id] = false
|
clients_ready[peer_id] = false
|
||||||
@@ -139,8 +146,13 @@ func _sync_existing_enemies_to_client(client_peer_id: int):
|
|||||||
_sync_enemy_spawn.rpc_id(client_peer_id, spawner.name, pos, scene_index, humanoid_type)
|
_sync_enemy_spawn.rpc_id(client_peer_id, spawner.name, pos, scene_index, humanoid_type)
|
||||||
print("GameWorld: Sent enemy spawn sync to client ", client_peer_id, ": spawner=", spawner.name, " pos=", pos, " scene_index=", scene_index, " humanoid_type=", humanoid_type)
|
print("GameWorld: Sent enemy spawn sync to client ", client_peer_id, ": spawner=", spawner.name, " pos=", pos, " scene_index=", scene_index, " humanoid_type=", humanoid_type)
|
||||||
|
|
||||||
func _on_player_disconnected(peer_id: int):
|
func _on_player_disconnected(peer_id: int, player_info: Dictionary):
|
||||||
print("GameWorld: Player disconnected - ", peer_id)
|
print("GameWorld: Player disconnected - ", peer_id)
|
||||||
|
|
||||||
|
# Send disconnect message to chat (only on server to avoid duplicates)
|
||||||
|
if multiplayer.is_server():
|
||||||
|
_send_player_disconnect_message(peer_id, player_info)
|
||||||
|
|
||||||
player_manager.despawn_players_for_peer(peer_id)
|
player_manager.despawn_players_for_peer(peer_id)
|
||||||
|
|
||||||
@rpc("authority", "reliable")
|
@rpc("authority", "reliable")
|
||||||
@@ -2030,6 +2042,60 @@ func _initialize_hud():
|
|||||||
else:
|
else:
|
||||||
print("GameWorld: HUD not found or not ready - this is OK if HUD scene is missing")
|
print("GameWorld: HUD not found or not ready - this is OK if HUD scene is missing")
|
||||||
|
|
||||||
|
func _create_chat_ui():
|
||||||
|
# Load chat UI scene
|
||||||
|
var chat_ui_scene = load("res://scenes/chat_ui.tscn")
|
||||||
|
if not chat_ui_scene:
|
||||||
|
push_error("GameWorld: Could not load chat_ui.tscn scene!")
|
||||||
|
return
|
||||||
|
|
||||||
|
var chat_ui = chat_ui_scene.instantiate()
|
||||||
|
if chat_ui:
|
||||||
|
add_child(chat_ui)
|
||||||
|
print("GameWorld: Chat UI scene instantiated and added to scene tree")
|
||||||
|
else:
|
||||||
|
push_error("GameWorld: Failed to instantiate chat_ui.tscn!")
|
||||||
|
|
||||||
|
func _send_player_join_message(peer_id: int, player_info: Dictionary):
|
||||||
|
# Send a chat message when a player joins
|
||||||
|
# Only send from server to avoid duplicate messages
|
||||||
|
if not multiplayer.is_server():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get player name (use first player name from the player_info)
|
||||||
|
var player_names = player_info.get("player_names", [])
|
||||||
|
var player_name = ""
|
||||||
|
if player_names.size() > 0:
|
||||||
|
player_name = player_names[0]
|
||||||
|
else:
|
||||||
|
player_name = "Player%d" % peer_id
|
||||||
|
|
||||||
|
# Get chat UI
|
||||||
|
var chat_ui = get_node_or_null("ChatUI")
|
||||||
|
if chat_ui and chat_ui.has_method("send_system_message"):
|
||||||
|
var message = "%s joined the game" % player_name
|
||||||
|
chat_ui.send_system_message(message)
|
||||||
|
|
||||||
|
func _send_player_disconnect_message(peer_id: int, player_info: Dictionary):
|
||||||
|
# Send a chat message when a player disconnects
|
||||||
|
# Only send from server to avoid duplicate messages
|
||||||
|
if not multiplayer.is_server():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get player name from player_info
|
||||||
|
var player_name = ""
|
||||||
|
var player_names = player_info.get("player_names", [])
|
||||||
|
if player_names.size() > 0:
|
||||||
|
player_name = player_names[0]
|
||||||
|
else:
|
||||||
|
player_name = "Player%d" % peer_id
|
||||||
|
|
||||||
|
# Get chat UI
|
||||||
|
var chat_ui = get_node_or_null("ChatUI")
|
||||||
|
if chat_ui and chat_ui.has_method("send_system_message"):
|
||||||
|
var message = "%s left/disconnected" % player_name
|
||||||
|
chat_ui.send_system_message(message)
|
||||||
|
|
||||||
func _create_level_complete_ui_programmatically() -> Node:
|
func _create_level_complete_ui_programmatically() -> Node:
|
||||||
# Create level complete UI programmatically
|
# Create level complete UI programmatically
|
||||||
var canvas_layer = CanvasLayer.new()
|
var canvas_layer = CanvasLayer.new()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ extends Node
|
|||||||
# Supports both hosting and joining games
|
# Supports both hosting and joining games
|
||||||
|
|
||||||
signal player_connected(peer_id, player_info)
|
signal player_connected(peer_id, player_info)
|
||||||
signal player_disconnected(peer_id)
|
signal player_disconnected(peer_id, player_info)
|
||||||
signal connection_failed()
|
signal connection_failed()
|
||||||
signal connection_succeeded()
|
signal connection_succeeded()
|
||||||
|
|
||||||
@@ -82,9 +82,12 @@ func _on_peer_connected(id: int):
|
|||||||
# Called when a peer disconnects
|
# Called when a peer disconnects
|
||||||
func _on_peer_disconnected(id: int):
|
func _on_peer_disconnected(id: int):
|
||||||
print("Peer disconnected: ", id)
|
print("Peer disconnected: ", id)
|
||||||
|
# Get player_info before erasing it
|
||||||
|
var player_info = {}
|
||||||
if players_info.has(id):
|
if players_info.has(id):
|
||||||
|
player_info = players_info[id]
|
||||||
players_info.erase(id)
|
players_info.erase(id)
|
||||||
player_disconnected.emit(id)
|
player_disconnected.emit(id, player_info)
|
||||||
|
|
||||||
# Called on client when successfully connected to server
|
# Called on client when successfully connected to server
|
||||||
func _on_connected_to_server():
|
func _on_connected_to_server():
|
||||||
|
|||||||
Reference in New Issue
Block a user