fix alot of shit for webrtc to work
This commit is contained in:
BIN
src/assets/audio/music/Gelhein - Evil.mp3
Normal file
BIN
src/assets/audio/music/Gelhein - Evil.mp3
Normal file
Binary file not shown.
19
src/assets/audio/music/Gelhein - Evil.mp3.import
Normal file
19
src/assets/audio/music/Gelhein - Evil.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://ba6csajuxujrg"
|
||||
path="res://.godot/imported/Gelhein - Evil.mp3-bc2ead9945dee5ecfc0f8aff80053da8.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/music/Gelhein - Evil.mp3"
|
||||
dest_files=["res://.godot/imported/Gelhein - Evil.mp3-bc2ead9945dee5ecfc0f8aff80053da8.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
@@ -79,7 +79,7 @@ dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
exclude_filter="webrtc/webrtc.gdextension"
|
||||
export_path="../export/www/index.html"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
|
||||
@@ -24,12 +24,14 @@ buses/default_bus_layout="uid://psistrevppd1"
|
||||
[autoload]
|
||||
|
||||
NetworkManager="*res://scripts/network_manager.gd"
|
||||
LogManager="*res://scripts/log_manager.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1280
|
||||
window/size/viewport_height=720
|
||||
window/stretch/mode="canvas_items"
|
||||
window/stretch/aspect="expand"
|
||||
window/stretch/scale_mode="integer"
|
||||
|
||||
[editor_plugins]
|
||||
@@ -65,6 +67,7 @@ move_down={
|
||||
grab={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
throw={
|
||||
@@ -75,6 +78,7 @@ throw={
|
||||
attack={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,27 @@ horizontal_alignment = 1
|
||||
layout_mode = 2
|
||||
texture_progress = ExtResource("4_hearts_filled")
|
||||
|
||||
[node name="CenterTop" type="MarginContainer" parent="." unique_id=22752256]
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.0
|
||||
offset_left = -150.0
|
||||
offset_top = 8.0
|
||||
offset_right = 150.0
|
||||
offset_bottom = 28.0
|
||||
grow_horizontal = 2
|
||||
theme = SubResource("Theme_standard_font")
|
||||
|
||||
[node name="LabelDisconnected" type="Label" parent="CenterTop" unique_id=869912310]
|
||||
layout_mode = 2
|
||||
theme = SubResource("Theme_standard_font")
|
||||
text = "Disconnected - Reconnecting..."
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
visible = false
|
||||
|
||||
[node name="MobileInput" type="Control" parent="." unique_id=1373461519]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
|
||||
@@ -103,6 +103,30 @@ layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "ruinborn.thefirstboss.com"
|
||||
|
||||
[node name="RoomFetchStatusContainer" type="VBoxContainer" parent="Control/MainMenu/VBoxContainer" unique_id=1286608618]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RoomFetchLoadingContainer" type="HBoxContainer" parent="Control/MainMenu/VBoxContainer/RoomFetchStatusContainer" unique_id=1286608619]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LoadingLabel" type="Label" parent="Control/MainMenu/VBoxContainer/RoomFetchStatusContainer/RoomFetchLoadingContainer" unique_id=1286608620]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Loading rooms..."
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="LoadingSpinner" type="Label" parent="Control/MainMenu/VBoxContainer/RoomFetchStatusContainer/RoomFetchLoadingContainer" unique_id=1286608621]
|
||||
layout_mode = 2
|
||||
text = "⏳"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LastFetchLabel" type="Label" parent="Control/MainMenu/VBoxContainer/RoomFetchStatusContainer" unique_id=1286608622]
|
||||
layout_mode = 2
|
||||
text = "Last fetched: Never"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="Spacer2" type="Control" parent="Control/MainMenu/VBoxContainer" unique_id=1061067008]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
@@ -156,7 +156,13 @@ func send_system_message(message: String):
|
||||
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
|
||||
# Route through game_world to avoid node path issues
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_receive_chat_message", ["System", message])
|
||||
else:
|
||||
# Fallback: try direct RPC if game_world not available
|
||||
_receive_message.rpc("System", message)
|
||||
else:
|
||||
# Client sends to server (system messages should only come from server)
|
||||
pass
|
||||
@@ -164,6 +170,16 @@ func send_system_message(message: String):
|
||||
# Offline mode - just show locally
|
||||
_add_message("System", message)
|
||||
|
||||
func add_local_message(player_name: String, message: String):
|
||||
# Add a local-only message (not broadcast to other players)
|
||||
# Used for connection status messages that should only appear locally
|
||||
_add_message(player_name, message)
|
||||
|
||||
func add_colorful_local_message(player_name: String, message: String):
|
||||
# Add a local-only message with each character in a different color
|
||||
# Used for special connection status messages
|
||||
_add_colorful_message(player_name, message)
|
||||
|
||||
func _send_message(message: String):
|
||||
if not network_manager:
|
||||
return
|
||||
@@ -176,7 +192,13 @@ func _send_message(message: String):
|
||||
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
|
||||
# Route through game_world to avoid node path issues
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_receive_chat_message", [player_name, message])
|
||||
else:
|
||||
# Fallback: try direct RPC if game_world not available
|
||||
_receive_message.rpc(player_name, message)
|
||||
else:
|
||||
# Client sends to server
|
||||
_send_message_to_server.rpc_id(1, player_name, message)
|
||||
@@ -190,8 +212,13 @@ func _send_message_to_server(player_name: String, message: String):
|
||||
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)
|
||||
# Then broadcast to all clients via game_world to avoid node path issues
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_receive_chat_message", [player_name, message])
|
||||
else:
|
||||
# Fallback: try direct RPC if game_world not available
|
||||
_receive_message.rpc(player_name, message)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _receive_message(player_name: String, message: String):
|
||||
@@ -217,7 +244,7 @@ func _get_player_color(player_name: String) -> Color:
|
||||
|
||||
func _add_message(player_name: String, message: String):
|
||||
if not message_list:
|
||||
print("ChatUI: ERROR - message_list is null, cannot add message!")
|
||||
LogManager.log_error("ChatUI: ERROR - message_list is null, cannot add message!", LogManager.CATEGORY_UI)
|
||||
return
|
||||
|
||||
# Get current time
|
||||
@@ -271,6 +298,83 @@ func _add_message(player_name: String, message: String):
|
||||
# Update background visibility
|
||||
_update_background_visibility()
|
||||
|
||||
func _add_colorful_message(player_name: String, message: String):
|
||||
# Add a message with each character in a different color
|
||||
if not message_list:
|
||||
LogManager.log_error("ChatUI: ERROR - message_list is null, cannot add message!", LogManager.CATEGORY_UI)
|
||||
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 for the player name
|
||||
var player_color = _get_player_color(player_name)
|
||||
var player_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
|
||||
|
||||
# Color palette for rainbow effect
|
||||
var colors = [
|
||||
Color.RED,
|
||||
Color.ORANGE,
|
||||
Color.YELLOW,
|
||||
Color.GREEN,
|
||||
Color.CYAN,
|
||||
Color.BLUE,
|
||||
Color(0.5, 0.0, 0.5), # Purple
|
||||
Color.MAGENTA
|
||||
]
|
||||
|
||||
# Format message with colored player name and colorful message text
|
||||
var formatted_text = "%s [color=%s]%s[/color]: " % [timestamp, player_color_hex, player_name]
|
||||
|
||||
# Add each character with a different color
|
||||
for i in range(message.length()):
|
||||
var char_text = message.substr(i, 1)
|
||||
var color = colors[i % colors.size()]
|
||||
var color_hex = "#%02x%02x%02x" % [int(color.r * 255), int(color.g * 255), int(color.b * 255)]
|
||||
formatted_text += "[color=%s]%s[/color]" % [color_hex, char_text]
|
||||
|
||||
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
|
||||
@@ -350,7 +454,17 @@ func _process(_delta):
|
||||
_update_message_fades()
|
||||
|
||||
func _get_player_name() -> String:
|
||||
# Get local player name
|
||||
# Get local player name from network_manager.players_info
|
||||
# This ensures consistency with how player names are generated and used elsewhere
|
||||
if network_manager and multiplayer.has_multiplayer_peer():
|
||||
var my_id = multiplayer.get_unique_id()
|
||||
var player_info = network_manager.get_player_info(my_id)
|
||||
if not player_info.is_empty():
|
||||
var player_names = player_info.get("player_names", [])
|
||||
if player_names.size() > 0:
|
||||
return player_names[0]
|
||||
|
||||
# Fallback: try to get from player node
|
||||
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")
|
||||
@@ -361,7 +475,7 @@ func _get_player_name() -> String:
|
||||
# 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
|
||||
# Final fallback
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
return "Player%d" % multiplayer.get_unique_id()
|
||||
return "Player"
|
||||
|
||||
@@ -101,6 +101,12 @@ func _ready() -> void:
|
||||
|
||||
# Call setup after a frame to ensure everything is ready
|
||||
call_deferred("_ready_after_setup")
|
||||
|
||||
# Ensure door_index meta is set if name follows BlockingDoor_<index>
|
||||
if name.begins_with("BlockingDoor_") and not has_meta("door_index"):
|
||||
var index_str = name.substr(13)
|
||||
if index_str.is_valid_int():
|
||||
set_meta("door_index", index_str.to_int())
|
||||
|
||||
func _update_door_texture():
|
||||
# Update door texture based on door type
|
||||
@@ -113,24 +119,24 @@ func _update_door_texture():
|
||||
var locked_texture = load("res://assets/gfx/door_locked.png")
|
||||
if locked_texture:
|
||||
sprite.texture = locked_texture
|
||||
print("Door: Set KeyDoor texture to door_locked.png")
|
||||
LogManager.log("Door: Set KeyDoor texture to door_locked.png", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
push_error("Door: Could not load door_locked.png texture!")
|
||||
LogManager.log_error("Door: Could not load door_locked.png texture!", LogManager.CATEGORY_DOOR)
|
||||
"GateDoor":
|
||||
var gate_texture = load("res://assets/gfx/door_gate.png")
|
||||
if gate_texture:
|
||||
sprite.texture = gate_texture
|
||||
print("Door: Set GateDoor texture to door_gate.png")
|
||||
LogManager.log("Door: Set GateDoor texture to door_gate.png", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
push_error("Door: Could not load door_gate.png texture!")
|
||||
LogManager.log_error("Door: Could not load door_gate.png texture!", LogManager.CATEGORY_DOOR)
|
||||
"StoneDoor":
|
||||
# Use door_barred.png for stone doors
|
||||
var barred_texture = load("res://assets/gfx/door_barred.png")
|
||||
if barred_texture:
|
||||
sprite.texture = barred_texture
|
||||
print("Door: Set StoneDoor texture to door_barred.png")
|
||||
LogManager.log("Door: Set StoneDoor texture to door_barred.png", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
push_error("Door: Could not load door_barred.png texture!")
|
||||
LogManager.log_error("Door: Could not load door_barred.png texture!", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
@@ -138,7 +144,7 @@ func _process(delta: float) -> void:
|
||||
if is_opening or is_closing:
|
||||
# Safety check: ensure closed_position is valid before animating
|
||||
if closed_position == Vector2.ZERO:
|
||||
print("Door: ERROR - closed_position is zero during animation! Resetting...")
|
||||
LogManager.log_error("Door: ERROR - closed_position is zero during animation! Resetting...", LogManager.CATEGORY_DOOR)
|
||||
closed_position = position - open_offset if is_opening else position
|
||||
is_opening = false
|
||||
is_closing = false
|
||||
@@ -160,7 +166,7 @@ func _process(delta: float) -> void:
|
||||
global_position = position # Also update global position during animation
|
||||
# Debug: log for KeyDoors to verify movement
|
||||
if type == "KeyDoor" and move_timer < 0.1: # Only log once at start of animation
|
||||
print("Door: KeyDoor opening animation - start: ", start_pos, ", target: ", target_pos, ", offset: ", open_offset, ", direction: ", direction)
|
||||
LogManager.log("Door: KeyDoor opening animation - start: " + str(start_pos) + ", target: " + str(target_pos) + ", offset: " + str(open_offset) + ", direction: " + str(direction), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# For KeyDoors: disable collision as soon as opening starts (allow passage immediately)
|
||||
# For StoneDoor/GateDoor: update collision based on position
|
||||
@@ -211,7 +217,8 @@ func _process(delta: float) -> void:
|
||||
global_position = open_position # Also set global position
|
||||
# When moved from closed position (open), collision should be DISABLED
|
||||
set_collision_layer_value(7, false)
|
||||
print("Door: Opening animation complete - moved to open position: ", open_position, " (closed: ", closed_position, ", offset: ", open_offset, ") - collision DISABLED", " (key_used=", key_used, ")" if type == "KeyDoor" else "")
|
||||
var key_used_str = " (key_used=" + str(key_used) + ")" if type == "KeyDoor" else ""
|
||||
LogManager.log("Door: Opening animation complete - moved to open position: " + str(open_position) + " (closed: " + str(closed_position) + ", offset: " + str(open_offset) + ") - collision DISABLED" + key_used_str, LogManager.CATEGORY_DOOR)
|
||||
|
||||
# CRITICAL: For KeyDoors, ensure key_used is true after animation completes
|
||||
# This prevents the door from being reset to closed in _process()
|
||||
@@ -234,7 +241,7 @@ func _process(delta: float) -> void:
|
||||
global_position = closed_position # Also set global position
|
||||
# When at closed position, collision should be ENABLED
|
||||
set_collision_layer_value(7, true)
|
||||
print("Door: Closing animation complete - moved to closed position: ", closed_position, " - collision ENABLED")
|
||||
LogManager.log("Door: Closing animation complete - moved to closed position: " + str(closed_position) + " - collision ENABLED", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# Spawn smoke puffs when StoneDoor finishes closing (1-3 puffs)
|
||||
if type == "StoneDoor":
|
||||
@@ -285,7 +292,7 @@ func _process(delta: float) -> void:
|
||||
# Snap to closed position if somehow moved (shouldn't happen, but safety check)
|
||||
var distance_to_closed = position.distance_to(closed_position)
|
||||
if distance_to_closed > 1.0:
|
||||
print("Door: KeyDoor was moved incorrectly! Resetting to closed position.")
|
||||
LogManager.log("Door: KeyDoor was moved incorrectly! Resetting to closed position.", LogManager.CATEGORY_DOOR)
|
||||
position = closed_position
|
||||
is_closed = true
|
||||
set_collision_layer_value(7, true)
|
||||
@@ -329,9 +336,9 @@ func _open():
|
||||
global_position = closed_position
|
||||
is_closed = true
|
||||
set_collision_layer_value(7, true) # Collision enabled at closed position
|
||||
print("Door: KeyDoor _open() called - reset to closed position ", closed_position, " before opening")
|
||||
LogManager.log("Door: KeyDoor _open() called - reset to closed position " + str(closed_position) + " before opening", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
push_error("Door: KeyDoor _open() called but closed_position is zero!")
|
||||
LogManager.log_error("Door: KeyDoor _open() called but closed_position is zero!", LogManager.CATEGORY_DOOR)
|
||||
return
|
||||
$SfxOpenKeyDoor.play()
|
||||
else:
|
||||
@@ -341,7 +348,7 @@ func _open():
|
||||
|
||||
if is_actually_open:
|
||||
# Door is already open - don't do anything
|
||||
print("Door: _open() called but door is already open! Position: ", position, ", closed: ", closed_position, ", distance: ", distance_to_closed)
|
||||
LogManager.log("Door: _open() called but door is already open! Position: " + str(position) + ", closed: " + str(closed_position) + ", distance: " + str(distance_to_closed), LogManager.CATEGORY_DOOR)
|
||||
# Ensure door is at open position and collision is disabled
|
||||
var open_pos = closed_position + open_offset
|
||||
position = open_pos
|
||||
@@ -356,9 +363,9 @@ func _open():
|
||||
global_position = closed_position
|
||||
is_closed = true
|
||||
set_collision_layer_value(7, true)
|
||||
print("Door: StoneDoor/GateDoor _open() called - ensuring door is at closed position ", closed_position, " before opening")
|
||||
LogManager.log("Door: StoneDoor/GateDoor _open() called - ensuring door is at closed position " + str(closed_position) + " before opening", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
push_error("Door: StoneDoor/GateDoor _open() called but closed_position is zero!")
|
||||
LogManager.log_error("Door: StoneDoor/GateDoor _open() called but closed_position is zero!", LogManager.CATEGORY_DOOR)
|
||||
return
|
||||
if type == "GateDoor":
|
||||
$SfxOpenGateDoor.play()
|
||||
@@ -367,16 +374,29 @@ func _open():
|
||||
|
||||
# CRITICAL: Store starting position for animation (should be closed_position)
|
||||
animation_start_position = position
|
||||
print("Door: Starting open animation from ", animation_start_position, " to ", closed_position + open_offset, " (offset: ", open_offset, ")")
|
||||
LogManager.log("Door: Starting open animation from " + str(animation_start_position) + " to " + str(closed_position + open_offset) + " (offset: " + str(open_offset) + ")", LogManager.CATEGORY_DOOR)
|
||||
is_opening = true
|
||||
is_closing = false
|
||||
move_timer = 0.0
|
||||
|
||||
# Sync door opening to clients in multiplayer
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server() and is_inside_tree():
|
||||
_sync_door_open.rpc()
|
||||
# Also sync puzzle_solved state
|
||||
_sync_puzzle_solved.rpc(puzzle_solved)
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_door_open_by_name", [name])
|
||||
# Also sync puzzle_solved state
|
||||
game_world._rpc_to_ready_peers("_sync_door_puzzle_solved_by_name", [name, puzzle_solved])
|
||||
|
||||
# Track door state for syncing to new clients
|
||||
if game_world:
|
||||
game_world.door_states[name] = {
|
||||
"is_closed": false,
|
||||
"puzzle_solved": puzzle_solved,
|
||||
"key_used": key_used if "key_used" in self else false,
|
||||
"position": position,
|
||||
"closed_position": closed_position,
|
||||
"open_offset": open_offset
|
||||
}
|
||||
|
||||
func _close():
|
||||
# Only close on server/authority in multiplayer, then sync to clients
|
||||
@@ -385,30 +405,30 @@ func _close():
|
||||
|
||||
# CRITICAL: KeyDoors should NEVER be closed (they only open with a key and stay open)
|
||||
if type == "KeyDoor":
|
||||
print("Door: ERROR - _close() called on KeyDoor! KeyDoors should never be closed!")
|
||||
LogManager.log_error("Door: ERROR - _close() called on KeyDoor! KeyDoors should never be closed!", LogManager.CATEGORY_DOOR)
|
||||
return
|
||||
|
||||
# Ensure closed_position is valid before closing
|
||||
if closed_position == Vector2.ZERO:
|
||||
# If closed_position wasn't set correctly, use current position
|
||||
closed_position = position
|
||||
print("Door: WARNING - closed_position was zero, using current position: ", closed_position)
|
||||
LogManager.log("Door: WARNING - closed_position was zero, using current position: " + str(closed_position), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# Check both flag and actual position to determine door state
|
||||
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
|
||||
var is_actually_at_closed = distance_to_closed < 5.0 # Within 5 pixels of closed position
|
||||
|
||||
print("Door: _close() called - is_closed: ", is_closed, ", is_actually_at_closed: ", is_actually_at_closed, ", position: ", position, ", closed: ", closed_position, ", distance: ", distance_to_closed)
|
||||
LogManager.log("Door: _close() called - is_closed: " + str(is_closed) + ", is_actually_at_closed: " + str(is_actually_at_closed) + ", position: " + str(position) + ", closed: " + str(closed_position) + ", distance: " + str(distance_to_closed), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# If door is already at closed position (both visually and by flag), don't do anything
|
||||
if is_closed and is_actually_at_closed and not is_opening and not is_closing:
|
||||
print("Door: Already closed (both flag and position match), not closing again")
|
||||
LogManager.log("Door: Already closed (both flag and position match), not closing again", LogManager.CATEGORY_DOOR)
|
||||
return # Already closed, don't do anything
|
||||
|
||||
# CRITICAL: If door is at closed position but flag says open, just fix the state - don't animate
|
||||
if is_actually_at_closed and not is_closed:
|
||||
# Door is visually at closed position but flag says open - fix state only
|
||||
print("Door: Door is at closed position but flag says open! Fixing state only (no animation)")
|
||||
LogManager.log("Door: Door is at closed position but flag says open! Fixing state only (no animation)", LogManager.CATEGORY_DOOR)
|
||||
position = closed_position # Ensure exact position
|
||||
is_closed = true
|
||||
set_collision_layer_value(7, true)
|
||||
@@ -424,7 +444,7 @@ func _close():
|
||||
# If door is significantly away from expected open position, snap to open position first
|
||||
if distance_to_open > 10.0:
|
||||
# Door is very far from expected open position - reset to open position first
|
||||
print("Door: WARNING - Door is far from expected open position! Resetting to open: ", expected_open_pos, " (was at: ", position, ", distance: ", distance_to_open, ")")
|
||||
LogManager.log("Door: WARNING - Door is far from expected open position! Resetting to open: " + str(expected_open_pos) + " (was at: " + str(position) + ", distance: " + str(distance_to_open) + ")", LogManager.CATEGORY_DOOR)
|
||||
animation_start_position = expected_open_pos
|
||||
position = expected_open_pos
|
||||
global_position = expected_open_pos
|
||||
@@ -434,7 +454,7 @@ func _close():
|
||||
# Door is at or near open position - use current position as start
|
||||
animation_start_position = position
|
||||
|
||||
print("Door: Starting close animation from ", animation_start_position, " to ", closed_position, " (offset: ", open_offset, ")")
|
||||
LogManager.log("Door: Starting close animation from " + str(animation_start_position) + " to " + str(closed_position) + " (offset: " + str(open_offset) + ")", LogManager.CATEGORY_DOOR)
|
||||
if type == "GateDoor":
|
||||
$SfxCloseGateDoor.play()
|
||||
else:
|
||||
@@ -446,7 +466,20 @@ func _close():
|
||||
|
||||
# Sync door closing to clients in multiplayer
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server() and is_inside_tree():
|
||||
_sync_door_close.rpc()
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_door_close_by_name", [name])
|
||||
|
||||
# Track door state for syncing to new clients
|
||||
if game_world:
|
||||
game_world.door_states[name] = {
|
||||
"is_closed": true,
|
||||
"puzzle_solved": puzzle_solved,
|
||||
"key_used": key_used if "key_used" in self else false,
|
||||
"position": position,
|
||||
"closed_position": closed_position,
|
||||
"open_offset": open_offset
|
||||
}
|
||||
|
||||
func _ready_after_setup():
|
||||
# Called after door is fully set up with room references and positioned
|
||||
@@ -454,7 +487,7 @@ func _ready_after_setup():
|
||||
# The position set by game_world is the OPEN position (initial state for blocking doors)
|
||||
var open_position = position # Current position is the OPEN position (from tile coordinates)
|
||||
|
||||
print("Door: _ready_after_setup() called - type: ", type, ", direction: ", direction, ", is_closed: ", is_closed, ", open_position: ", open_position)
|
||||
LogManager.log("Door: _ready_after_setup() called - type: " + str(type) + ", direction: " + str(direction) + ", is_closed: " + str(is_closed) + ", open_position: " + str(open_position), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# CRITICAL: Calculate closed position based on direction
|
||||
# For StoneDoor/GateDoor: They start OPEN, then CLOSE when entering room
|
||||
@@ -491,7 +524,7 @@ func _ready_after_setup():
|
||||
# This is used when opening from closed position
|
||||
open_offset = -closed_offset # open_offset = (0, -16) means open is 16px up from closed
|
||||
|
||||
print("Door: Calculated positions - open: ", open_position, ", closed: ", closed_position, ", closed_offset: ", closed_offset, ", open_offset: ", open_offset)
|
||||
LogManager.log("Door: Calculated positions - open: " + str(open_position) + ", closed: " + str(closed_position) + ", closed_offset: " + str(closed_offset) + ", open_offset: " + str(open_offset), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# CRITICAL: KeyDoors should ALWAYS start closed, regardless of is_closed value
|
||||
# KeyDoors should NEVER be moved until opened with a key
|
||||
@@ -523,7 +556,7 @@ func _ready_after_setup():
|
||||
position = closed_position
|
||||
global_position = closed_position
|
||||
set_collision_layer_value(7, true) # Collision enabled when closed
|
||||
print("Door: KeyDoor starting CLOSED at position ", position, " (direction: ", direction, "), will open to ", closed_position + open_offset, " - collision ENABLED")
|
||||
LogManager.log("Door: KeyDoor starting CLOSED at position " + str(position) + " (direction: " + str(direction) + "), will open to " + str(closed_position + open_offset) + " - collision ENABLED", LogManager.CATEGORY_DOOR)
|
||||
# Create key indicator sprite for KeyDoor
|
||||
_create_key_indicator()
|
||||
return # Exit early for KeyDoors
|
||||
@@ -533,46 +566,46 @@ func _ready_after_setup():
|
||||
global_position = closed_position
|
||||
is_closed = true # Ensure state matches position
|
||||
set_collision_layer_value(7, true)
|
||||
print("Door: Starting CLOSED at position ", position, " (type: ", type, ", direction: ", direction, ") - collision ENABLED")
|
||||
LogManager.log("Door: Starting CLOSED at position " + str(position) + " (type: " + str(type) + ", direction: " + str(direction) + ") - collision ENABLED", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
# StoneDoor/GateDoor starting OPEN (default for blocking doors)
|
||||
# CRITICAL: Door MUST start at open position (which is where game_world placed it)
|
||||
# Ensure position is EXACTLY at open_position (don't assume game_world set it correctly)
|
||||
if position.distance_to(open_position) > 1.0:
|
||||
# Position doesn't match open_position - force it to open position
|
||||
print("Door: WARNING - Position doesn't match open_position! Forcing to open: ", open_position, " (was: ", position, ")")
|
||||
LogManager.log("Door: WARNING - Position doesn't match open_position! Forcing to open: " + str(open_position) + " (was: " + str(position) + ")", LogManager.CATEGORY_DOOR)
|
||||
position = open_position
|
||||
|
||||
global_position = position # Ensure global_position matches position
|
||||
is_closed = false # CRITICAL: State MUST be false (open) when at open position
|
||||
set_collision_layer_value(7, false) # CRITICAL: Collision MUST be DISABLED when open
|
||||
print("Door: Starting OPEN at position ", position, " (closed: ", closed_position, ", open: ", open_position, ", open_offset: ", open_offset, ", type: ", type, ", direction: ", direction, ") - collision DISABLED, is_closed: ", is_closed)
|
||||
LogManager.log("Door: Starting OPEN at position " + str(position) + " (closed: " + str(closed_position) + ", open: " + str(open_position) + ", open_offset: " + str(open_offset) + ", type: " + str(type) + ", direction: " + str(direction) + ") - collision DISABLED, is_closed: " + str(is_closed), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# CRITICAL: Verify the door is actually at open position after setting it
|
||||
var actual_distance = position.distance_to(closed_position)
|
||||
var expected_distance = 16.0 # Should be 16 pixels away
|
||||
if abs(actual_distance - expected_distance) > 2.0:
|
||||
push_error("Door: ERROR - Door open/closed distance is wrong! Position: ", position, ", closed: ", closed_position, ", distance: ", actual_distance, " (expected: ", expected_distance, ")")
|
||||
LogManager.log_error("Door: ERROR - Door open/closed distance is wrong! Position: " + str(position) + ", closed: " + str(closed_position) + ", distance: " + str(actual_distance) + " (expected: " + str(expected_distance) + ")", LogManager.CATEGORY_DOOR)
|
||||
# Force it to correct open position
|
||||
position = open_position
|
||||
global_position = open_position
|
||||
is_closed = false # CRITICAL: Ensure state is false when at open position
|
||||
set_collision_layer_value(7, false)
|
||||
print("Door: FORCED door to open position: ", position, " (distance to closed: ", position.distance_to(closed_position), ", is_closed: ", is_closed, ")")
|
||||
LogManager.log("Door: FORCED door to open position: " + str(position) + " (distance to closed: " + str(position.distance_to(closed_position)) + ", is_closed: " + str(is_closed) + ")", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# FINAL VERIFICATION: Double-check state matches position
|
||||
var distance_to_closed = position.distance_to(closed_position)
|
||||
var should_be_open = distance_to_closed > 8.0 # If more than 8px from closed, should be open
|
||||
if should_be_open and is_closed:
|
||||
push_error("Door: ERROR - Door is at open position but is_closed is true! Fixing state...")
|
||||
LogManager.log_error("Door: ERROR - Door is at open position but is_closed is true! Fixing state...", LogManager.CATEGORY_DOOR)
|
||||
is_closed = false
|
||||
set_collision_layer_value(7, false)
|
||||
print("Door: Fixed state - door is now OPEN (is_closed: ", is_closed, ", collision: ", get_collision_layer_value(7), ")")
|
||||
LogManager.log("Door: Fixed state - door is now OPEN (is_closed: " + str(is_closed) + ", collision: " + str(get_collision_layer_value(7)) + ")", LogManager.CATEGORY_DOOR)
|
||||
elif not should_be_open and not is_closed:
|
||||
push_error("Door: ERROR - Door is at closed position but is_closed is false! Fixing state...")
|
||||
LogManager.log_error("Door: ERROR - Door is at closed position but is_closed is false! Fixing state...", LogManager.CATEGORY_DOOR)
|
||||
is_closed = true
|
||||
set_collision_layer_value(7, true)
|
||||
print("Door: Fixed state - door is now CLOSED (is_closed: ", is_closed, ", collision: ", get_collision_layer_value(7), ")")
|
||||
LogManager.log("Door: Fixed state - door is now CLOSED (is_closed: " + str(is_closed) + ", collision: " + str(get_collision_layer_value(7)) + ")", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# NOTE: Doors are NOT connected via signals to room triggers
|
||||
# Instead, room triggers call door._on_room_entered() directly
|
||||
@@ -634,20 +667,20 @@ func _on_room_entered(body):
|
||||
var distance_to_closed = position.distance_to(closed_position) if closed_position != Vector2.ZERO else 999.0
|
||||
var is_actually_open = distance_to_closed > 5.0 # If door is more than 5 pixels away from closed_position, it's open
|
||||
|
||||
print("Door: _on_room_entered() - type: ", type, ", is_closed: ", is_closed, ", is_actually_open: ", is_actually_open, ", position: ", position, ", closed: ", closed_position, ", distance: ", distance_to_closed)
|
||||
LogManager.log("Door: _on_room_entered() - type: " + str(type) + ", is_closed: " + str(is_closed) + ", is_actually_open: " + str(is_actually_open) + ", position: " + str(position) + ", closed: " + str(closed_position) + ", distance: " + str(distance_to_closed), LogManager.CATEGORY_DOOR)
|
||||
|
||||
# CRITICAL: Only close if door is actually open (both flag and position must indicate open)
|
||||
# If door is already closed, don't do anything
|
||||
if is_actually_open and not is_closing and not is_opening:
|
||||
# Door is actually open (position is away from closed position) - close it
|
||||
print("Door: Closing door on room entry - was at position ", position, " (closed: ", closed_position, ", is_closed: ", is_closed, ", distance: ", distance_to_closed, ")")
|
||||
LogManager.log("Door: Closing door on room entry - was at position " + str(position) + " (closed: " + str(closed_position) + ", is_closed: " + str(is_closed) + ", distance: " + str(distance_to_closed) + ")", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# Ensure door is at open position before closing
|
||||
var expected_open_pos = closed_position + open_offset
|
||||
var dist_to_open = position.distance_to(expected_open_pos)
|
||||
if dist_to_open > 5.0:
|
||||
# Door is not at expected open position - reset to open position first
|
||||
print("Door: WARNING - Door is not at expected open position! Resetting to open: ", expected_open_pos, " (was at: ", position, ")")
|
||||
LogManager.log("Door: WARNING - Door is not at expected open position! Resetting to open: " + str(expected_open_pos) + " (was at: " + str(position) + ")", LogManager.CATEGORY_DOOR)
|
||||
position = expected_open_pos
|
||||
global_position = expected_open_pos
|
||||
is_closed = false
|
||||
@@ -659,20 +692,20 @@ func _on_room_entered(body):
|
||||
return # Exit early, don't check puzzle state yet
|
||||
elif is_actually_open:
|
||||
# Door is open but animation already in progress - don't interfere
|
||||
print("Door: Door is open but animation in progress, not closing")
|
||||
LogManager.log("Door: Door is open but animation in progress, not closing", LogManager.CATEGORY_DOOR)
|
||||
return
|
||||
elif not is_actually_open:
|
||||
# Door is already at closed position - but for StoneDoor/GateDoor, this shouldn't happen on room entry
|
||||
# They should start OPEN and then CLOSE when entering room
|
||||
# If door is at closed position, it might have been closed already - don't do anything
|
||||
print("Door: WARNING - Door is already at closed position when entering room! This shouldn't happen for StoneDoor/GateDoor that start open.")
|
||||
LogManager.log("Door: WARNING - Door is already at closed position when entering room! This shouldn't happen for StoneDoor/GateDoor that start open.", LogManager.CATEGORY_DOOR)
|
||||
if closed_position != Vector2.ZERO:
|
||||
# Ensure exact position and state match
|
||||
position = closed_position
|
||||
global_position = closed_position
|
||||
is_closed = true
|
||||
set_collision_layer_value(7, true) # Collision ENABLED when closed
|
||||
print("Door: Door was already closed - ensuring state is correct, position: ", position, ", closed: ", closed_position)
|
||||
LogManager.log("Door: Door was already closed - ensuring state is correct, position: " + str(position) + ", closed: " + str(closed_position), LogManager.CATEGORY_DOOR)
|
||||
# Now that door is confirmed closed, check if puzzle is already solved
|
||||
# CRITICAL: Only check puzzle state if door is closed - don't check if puzzle is already solved
|
||||
if not puzzle_solved:
|
||||
@@ -694,7 +727,7 @@ func _check_puzzle_state():
|
||||
# CRITICAL: Don't check puzzle state while door is animating (closing or opening)
|
||||
# This prevents race conditions where switch triggers before door finishes closing
|
||||
if is_closing or is_opening:
|
||||
print("Door: Skipping puzzle check - door is animating (is_closing: ", is_closing, ", is_opening: ", is_opening, ")")
|
||||
LogManager.log("Door: Skipping puzzle check - door is animating (is_closing: " + str(is_closing) + ", is_opening: " + str(is_opening) + ")", LogManager.CATEGORY_DOOR)
|
||||
return
|
||||
|
||||
# Check door's actual state (position-based check is more reliable than flags)
|
||||
@@ -708,13 +741,15 @@ func _check_puzzle_state():
|
||||
# This handles race conditions where switch triggers while door is still closing
|
||||
if puzzle_solved and (not is_actually_open or collision_enabled):
|
||||
# Door should be open but isn't (position or collision) - reset puzzle_solved to allow switch to trigger again
|
||||
print("Door: puzzle_solved is true but door is not actually open (position: ", is_actually_open, ", collision: ", collision_enabled, ") - resetting to allow switch to trigger again")
|
||||
LogManager.log("Door: puzzle_solved is true but door is not actually open (position: " + str(is_actually_open) + ", collision: " + str(collision_enabled) + ") - resetting to allow switch to trigger again", LogManager.CATEGORY_DOOR)
|
||||
puzzle_solved = false
|
||||
switches_activated = false
|
||||
|
||||
# Check if all enemies are defeated (enemies in blocking room)
|
||||
if requires_enemies and _are_all_enemies_defeated():
|
||||
print("Door: All enemies defeated! Opening door ", name, " (type: ", type, ", room: ", blocking_room.get("x", "?") if blocking_room and not blocking_room.is_empty() else "?", ",", blocking_room.get("y", "?") if blocking_room and not blocking_room.is_empty() else "?", ")")
|
||||
var room_x_str = str(blocking_room.get("x", "?")) if blocking_room and not blocking_room.is_empty() else "?"
|
||||
var room_y_str = str(blocking_room.get("y", "?")) if blocking_room and not blocking_room.is_empty() else "?"
|
||||
LogManager.log("Door: All enemies defeated! Opening door " + str(name) + " (type: " + str(type) + ", room: " + room_x_str + "," + room_y_str + ")", LogManager.CATEGORY_DOOR)
|
||||
enemies_defeated = true
|
||||
puzzle_solved = true
|
||||
if is_actually_closed:
|
||||
@@ -744,7 +779,7 @@ func _check_puzzle_state():
|
||||
# Not all switches are active
|
||||
if puzzle_solved and has_pillar_switch:
|
||||
# Pillar switch became inactive and door was open - close it and reset puzzle
|
||||
print("Door: Pillar switch deactivated - closing door ", name)
|
||||
LogManager.log("Door: Pillar switch deactivated - closing door " + str(name), LogManager.CATEGORY_DOOR)
|
||||
switches_activated = false
|
||||
puzzle_solved = false
|
||||
if not is_actually_closed:
|
||||
@@ -768,9 +803,13 @@ func _are_all_enemies_defeated() -> bool:
|
||||
return false
|
||||
|
||||
# Find all enemies in the room that were spawned from spawners
|
||||
var entities_node = get_tree().get_first_node_in_group("game_world")
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var entities_node = null
|
||||
if game_world:
|
||||
entities_node = game_world.get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
entities_node = get_node("/root/GameWorld/Entities")
|
||||
# Fallback without throwing if GameWorld isn't ready yet
|
||||
entities_node = get_node_or_null("/root/GameWorld/Entities")
|
||||
|
||||
if not entities_node:
|
||||
return false
|
||||
@@ -804,10 +843,14 @@ func _are_all_enemies_defeated() -> bool:
|
||||
|
||||
if enemy_in_room:
|
||||
room_spawned_enemies.append(child)
|
||||
print("Door: Found spawned enemy in room: ", child.name, " (spawner: ", child.get_meta("spawner_name") if child.has_meta("spawner_name") else "unknown", ", is_dead: ", child.is_dead if "is_dead" in child else "unknown", ")")
|
||||
var spawner_name = str(child.get_meta("spawner_name")) if child.has_meta("spawner_name") else "unknown"
|
||||
var is_dead_str = str(child.is_dead) if "is_dead" in child else "unknown"
|
||||
LogManager.log("Door: Found spawned enemy in room: " + str(child.name) + " (spawner: " + spawner_name + ", is_dead: " + is_dead_str + ")", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# Check if all spawned enemies are dead
|
||||
print("Door: _are_all_enemies_defeated() - Found ", room_spawned_enemies.size(), " spawned enemies in room (", target_room.get("x", "?") if target_room and not target_room.is_empty() else "?", ",", target_room.get("y", "?") if target_room and not target_room.is_empty() else "?", ")")
|
||||
var target_room_x_str = str(target_room.get("x", "?")) if target_room and not target_room.is_empty() else "?"
|
||||
var target_room_y_str = str(target_room.get("y", "?")) if target_room and not target_room.is_empty() else "?"
|
||||
LogManager.log("Door: _are_all_enemies_defeated() - Found " + str(room_spawned_enemies.size()) + " spawned enemies in room (" + target_room_x_str + "," + target_room_y_str + ")", LogManager.CATEGORY_DOOR)
|
||||
|
||||
# First, check if any enemies in room_spawned_enemies are still alive
|
||||
# If any are alive, puzzle is not solved
|
||||
@@ -820,12 +863,12 @@ func _are_all_enemies_defeated() -> bool:
|
||||
enemy_is_dead = enemy.is_queued_for_deletion() or not enemy.is_inside_tree()
|
||||
|
||||
if not enemy_is_dead:
|
||||
print("Door: Enemy ", enemy.name, " is still alive - puzzle not solved yet")
|
||||
LogManager.log("Door: Enemy " + str(enemy.name) + " is still alive - puzzle not solved yet", LogManager.CATEGORY_DOOR)
|
||||
return false # Enemy is still alive, puzzle not solved
|
||||
|
||||
# If we have enemies and all are dead, puzzle is solved
|
||||
if room_spawned_enemies.size() > 0:
|
||||
print("Door: All ", room_spawned_enemies.size(), " spawned enemies are dead! Puzzle solved!")
|
||||
LogManager.log("Door: All " + str(room_spawned_enemies.size()) + " spawned enemies are dead! Puzzle solved!", LogManager.CATEGORY_DOOR)
|
||||
return true # All enemies found are dead
|
||||
|
||||
# No spawned enemies found - check if spawners have actually spawned enemies before
|
||||
@@ -836,9 +879,9 @@ func _are_all_enemies_defeated() -> bool:
|
||||
# This catches cases where enemies weren't added to room_spawned_enemies due to position check issues
|
||||
var entities_child = entities_node.get_node_or_null("Entities") if entities_node else null
|
||||
if not entities_child and entities_node:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
entities_child = game_world.get_node_or_null("Entities")
|
||||
var fallback_game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if fallback_game_world:
|
||||
entities_child = fallback_game_world.get_node_or_null("Entities")
|
||||
|
||||
var tile_size = 16
|
||||
var room_min_x = target_room.x + 2
|
||||
@@ -871,7 +914,7 @@ func _are_all_enemies_defeated() -> bool:
|
||||
|
||||
if enemy_is_alive:
|
||||
# Found an ALIVE enemy in this room - puzzle not solved!
|
||||
print("Door: Found ALIVE enemy ", child.name, " in room - puzzle not solved yet (enemy still alive)")
|
||||
LogManager.log("Door: Found ALIVE enemy " + str(child.name) + " in room - puzzle not solved yet (enemy still alive)", LogManager.CATEGORY_DOOR)
|
||||
return false
|
||||
|
||||
# No alive enemies found in room - now check if spawners have spawned
|
||||
@@ -907,9 +950,9 @@ func _are_all_enemies_defeated() -> bool:
|
||||
if not has_spawned:
|
||||
var entities_child_for_spawner = entities_node.get_node_or_null("Entities") if entities_node else null
|
||||
if not entities_child_for_spawner and entities_node:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
entities_child_for_spawner = game_world.get_node_or_null("Entities")
|
||||
var fallback_game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if fallback_game_world:
|
||||
entities_child_for_spawner = fallback_game_world.get_node_or_null("Entities")
|
||||
|
||||
if entities_child_for_spawner:
|
||||
for child in entities_child_for_spawner.get_children():
|
||||
@@ -1002,7 +1045,7 @@ func _are_all_enemies_defeated() -> bool:
|
||||
for spawner_name in unique_spawner_names_that_spawned.keys():
|
||||
spawners_in_room.append(null) # Placeholder for destroyed spawner
|
||||
spawners_that_have_spawned.append(null) # Count as spawned
|
||||
print("Door: Spawner ", spawner_name, " was destroyed but spawned enemies that are now all dead - counting as spawned")
|
||||
LogManager.log("Door: Spawner " + str(spawner_name) + " was destroyed but spawned enemies that are now all dead - counting as spawned", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
# Spawners exist - check if any weren't counted as spawned yet
|
||||
for spawner_name in unique_spawner_names_that_spawned.keys():
|
||||
@@ -1017,7 +1060,7 @@ func _are_all_enemies_defeated() -> bool:
|
||||
var spawner = spawners_in_room[i]
|
||||
if is_instance_valid(spawner) and spawner.name == spawner_name:
|
||||
spawners_that_have_spawned.append(spawner)
|
||||
print("Door: Found dead enemy from spawner ", spawner_name, " - marking as spawned")
|
||||
LogManager.log("Door: Found dead enemy from spawner " + str(spawner_name) + " - marking as spawned", LogManager.CATEGORY_DOOR)
|
||||
break
|
||||
|
||||
|
||||
@@ -1034,7 +1077,7 @@ func _are_all_enemies_defeated() -> bool:
|
||||
if valid_spawners_count > 0 and valid_spawned_count >= valid_spawners_count:
|
||||
# All spawners in room have spawned at least once, and no enemies found in room
|
||||
# This means all spawned enemies are dead - puzzle solved!
|
||||
print("Door: No spawned enemies found, but all ", valid_spawners_count, " spawners in room have spawned enemies that are all dead - puzzle solved!")
|
||||
LogManager.log("Door: No spawned enemies found, but all " + str(valid_spawners_count) + " spawners in room have spawned enemies that are all dead - puzzle solved!", LogManager.CATEGORY_DOOR)
|
||||
return true
|
||||
|
||||
# Also check: if no spawners found (they were destroyed), but this is a puzzle room (has blocking doors),
|
||||
@@ -1043,13 +1086,13 @@ func _are_all_enemies_defeated() -> bool:
|
||||
if valid_spawners_count == 0 and valid_spawned_count > 0:
|
||||
# Spawners were destroyed, but we found evidence they spawned
|
||||
# Since no enemies found, they must all be dead - puzzle solved!
|
||||
print("Door: No spawners or enemies found, but found evidence of spawned enemies that are now all dead - puzzle solved!")
|
||||
LogManager.log("Door: No spawners or enemies found, but found evidence of spawned enemies that are now all dead - puzzle solved!", LogManager.CATEGORY_DOOR)
|
||||
return true
|
||||
|
||||
if valid_spawners_count > 0:
|
||||
print("Door: Spawners in room (", valid_spawners_count, ") but only ", valid_spawned_count, " have spawned - puzzle not solved yet")
|
||||
LogManager.log("Door: Spawners in room (" + str(valid_spawners_count) + ") but only " + str(valid_spawned_count) + " have spawned - puzzle not solved yet", LogManager.CATEGORY_DOOR)
|
||||
else:
|
||||
print("Door: No spawned enemies found in room - puzzle not solved yet (enemies may not have spawned or already removed)")
|
||||
LogManager.log("Door: No spawned enemies found in room - puzzle not solved yet (enemies may not have spawned or already removed)", LogManager.CATEGORY_DOOR)
|
||||
return false
|
||||
|
||||
for enemy in room_spawned_enemies:
|
||||
@@ -1062,13 +1105,13 @@ func _are_all_enemies_defeated() -> bool:
|
||||
enemy_is_dead = enemy.is_queued_for_deletion() or not enemy.is_inside_tree()
|
||||
|
||||
if not enemy_is_dead:
|
||||
print("Door: Enemy ", enemy.name, " is still alive (is_dead: ", enemy_is_dead, ", is_queued: ", enemy.is_queued_for_deletion(), ", in_tree: ", enemy.is_inside_tree(), ")")
|
||||
LogManager.log("Door: Enemy " + str(enemy.name) + " is still alive (is_dead: " + str(enemy_is_dead) + ", is_queued: " + str(enemy.is_queued_for_deletion()) + ", in_tree: " + str(enemy.is_inside_tree()) + ")", LogManager.CATEGORY_DOOR)
|
||||
return false
|
||||
else:
|
||||
# Enemy is no longer valid (removed from scene) - consider it dead
|
||||
print("Door: Enemy is no longer valid (removed from scene) - counting as dead")
|
||||
LogManager.log("Door: Enemy is no longer valid (removed from scene) - counting as dead", LogManager.CATEGORY_DOOR)
|
||||
|
||||
print("Door: All ", room_spawned_enemies.size(), " spawned enemies are dead! Puzzle solved!")
|
||||
LogManager.log("Door: All " + str(room_spawned_enemies.size()) + " spawned enemies are dead! Puzzle solved!", LogManager.CATEGORY_DOOR)
|
||||
return true # All enemies are dead
|
||||
|
||||
func _spawn_smoke_puffs_on_close():
|
||||
@@ -1135,20 +1178,22 @@ func _are_all_switches_activated() -> bool:
|
||||
# Do NOT use position-based fallback checks - they cause cross-room door triggering!
|
||||
if connected_switches.size() > 0:
|
||||
# Check all connected switches (these are the switches in THIS door's puzzle room)
|
||||
print("Door: _are_all_switches_activated() - Checking ", connected_switches.size(), " connected switches for door ", name, " (room: ", blocking_room.get("x", "?"), ",", blocking_room.get("y", "?"), ")")
|
||||
var switch_room_x = str(blocking_room.get("x", "?")) if blocking_room and not blocking_room.is_empty() else "?"
|
||||
var switch_room_y = str(blocking_room.get("y", "?")) if blocking_room and not blocking_room.is_empty() else "?"
|
||||
LogManager.log("Door: _are_all_switches_activated() - Checking " + str(connected_switches.size()) + " connected switches for door " + str(name) + " (room: " + switch_room_x + "," + switch_room_y + ")", LogManager.CATEGORY_DOOR)
|
||||
for switch in connected_switches:
|
||||
if not is_instance_valid(switch):
|
||||
continue
|
||||
# is_activated is a variable, not a method
|
||||
if not switch.is_activated:
|
||||
print("Door: Switch ", switch.name, " is NOT activated")
|
||||
LogManager.log("Door: Switch " + str(switch.name) + " is NOT activated", LogManager.CATEGORY_DOOR)
|
||||
return false
|
||||
print("Door: All connected switches are activated!")
|
||||
LogManager.log("Door: All connected switches are activated!", LogManager.CATEGORY_DOOR)
|
||||
return true # All connected switches are activated
|
||||
|
||||
# CRITICAL: If no switches are connected, the puzzle is NOT solved!
|
||||
# Switches should ALWAYS be connected when spawned - if they're not, it's an error
|
||||
print("Door: WARNING - Door ", name, " has no connected switches! Puzzle cannot be solved!")
|
||||
LogManager.log("Door: WARNING - Door " + str(name) + " has no connected switches! Puzzle cannot be solved!", LogManager.CATEGORY_DOOR)
|
||||
return false # No connected switches means puzzle is NOT solved
|
||||
|
||||
func _on_key_interaction_area_body_entered(body):
|
||||
@@ -1165,7 +1210,7 @@ func _on_key_interaction_area_body_entered(body):
|
||||
key_used = true
|
||||
_show_key_indicator()
|
||||
_open()
|
||||
print("KeyDoor opened with key!")
|
||||
LogManager.log("KeyDoor opened with key!", LogManager.CATEGORY_DOOR)
|
||||
|
||||
func _show_key_indicator():
|
||||
# Show key indicator above door
|
||||
@@ -1253,7 +1298,8 @@ func _sync_door_open():
|
||||
else:
|
||||
$SfxOpenStoneDoor.play()
|
||||
|
||||
print("Door: Client received door open RPC for ", name, " - starting open animation", " (key_used=", key_used, ")" if type == "KeyDoor" else "")
|
||||
var key_used_str = " (key_used=" + str(key_used) + ")" if type == "KeyDoor" else ""
|
||||
LogManager.log("Door: Client received door open RPC for " + str(name) + " - starting open animation" + key_used_str, LogManager.CATEGORY_DOOR)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_puzzle_solved(is_solved: bool):
|
||||
@@ -1263,7 +1309,7 @@ func _sync_puzzle_solved(is_solved: bool):
|
||||
if is_solved:
|
||||
enemies_defeated = true
|
||||
switches_activated = true
|
||||
print("Door: Client received puzzle_solved sync for ", name, " - puzzle_solved: ", is_solved)
|
||||
LogManager.log("Door: Client received puzzle_solved sync for " + str(name) + " - puzzle_solved: " + str(is_solved), LogManager.CATEGORY_DOOR)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_door_close():
|
||||
@@ -1289,4 +1335,4 @@ func _sync_door_close():
|
||||
is_closing = true
|
||||
move_timer = 0.0
|
||||
|
||||
print("Door: Client received door close RPC for ", name, " - starting close animation")
|
||||
LogManager.log("Door: Client received door close RPC for " + str(name) + " - starting close animation", LogManager.CATEGORY_DOOR)
|
||||
|
||||
@@ -96,7 +96,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
# Calculate target room count based on level
|
||||
# Level 1: 7-8 rooms, then increase by 2-3 rooms per level
|
||||
var target_room_count = 7 + (level - 1) * 2 + rng.randi_range(0, 1) # Level 1: 7-8, Level 2: 9-10, etc.
|
||||
print("DungeonGenerator: Level ", level, " - Target room count: ", target_room_count)
|
||||
LogManager.log("DungeonGenerator: Level " + str(level) + " - Target room count: " + str(target_room_count), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Initialize grid (0 = wall, 1 = floor, 2 = door, 3 = corridor)
|
||||
var grid = []
|
||||
@@ -137,7 +137,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
|
||||
attempts -= 1
|
||||
|
||||
print("DungeonGenerator: Generated ", all_rooms.size(), " rooms (target was ", target_room_count, ")")
|
||||
LogManager.log("DungeonGenerator: Generated " + str(all_rooms.size()) + " rooms (target was " + str(target_room_count) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# 3. Connect rooms with corridors/doors
|
||||
if all_rooms.size() > 1:
|
||||
@@ -156,7 +156,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
# 6. Mark exit room (farthest REACHABLE room from start)
|
||||
# First find all reachable rooms from start
|
||||
var reachable_rooms = _find_reachable_rooms(all_rooms[start_room_index], all_rooms, all_doors)
|
||||
print("DungeonGenerator: Found ", reachable_rooms.size(), " reachable rooms from start (out of ", all_rooms.size(), " total)")
|
||||
LogManager.log("DungeonGenerator: Found " + str(reachable_rooms.size()) + " reachable rooms from start (out of " + str(all_rooms.size()) + " total)", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# CRITICAL: Remove inaccessible rooms (rooms not reachable from start)
|
||||
# Store the start room before filtering (it should always be reachable)
|
||||
@@ -179,13 +179,13 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
filtered_rooms.append(room)
|
||||
else:
|
||||
inaccessible_count += 1
|
||||
print("DungeonGenerator: Removing inaccessible room at (", room.x, ", ", room.y, ") - no corridor connection")
|
||||
LogManager.log("DungeonGenerator: Removing inaccessible room at (" + str(room.x) + ", " + str(room.y) + ") - no corridor connection", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Update all_rooms to only include reachable rooms
|
||||
all_rooms = filtered_rooms
|
||||
|
||||
if inaccessible_count > 0:
|
||||
print("DungeonGenerator: Removed ", inaccessible_count, " inaccessible room(s). Remaining rooms: ", all_rooms.size())
|
||||
LogManager.log("DungeonGenerator: Removed " + str(inaccessible_count) + " inaccessible room(s). Remaining rooms: " + str(all_rooms.size()), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Update start_room_index after filtering (find start room in new array using value-based comparison)
|
||||
start_room_index = -1
|
||||
@@ -197,7 +197,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
break
|
||||
|
||||
if start_room_index == -1:
|
||||
push_error("DungeonGenerator: ERROR - Start room was removed! This should never happen!")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Start room was removed! This should never happen!", LogManager.CATEGORY_DUNGEON)
|
||||
start_room_index = 0 # Fallback
|
||||
|
||||
# Also remove doors connected to inaccessible rooms (clean up all_doors)
|
||||
@@ -229,23 +229,23 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
filtered_doors.append(door)
|
||||
else:
|
||||
doors_removed += 1
|
||||
print("DungeonGenerator: Removing door - room1 reachable: ", door_room1_reachable, ", room2 reachable: ", door_room2_reachable)
|
||||
LogManager.log("DungeonGenerator: Removing door - room1 reachable: " + str(door_room1_reachable) + ", room2 reachable: " + str(door_room2_reachable), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
all_doors = filtered_doors
|
||||
if doors_removed > 0:
|
||||
print("DungeonGenerator: Removed ", doors_removed, " door(s) connected to inaccessible rooms. Remaining doors: ", all_doors.size())
|
||||
LogManager.log("DungeonGenerator: Removed " + str(doors_removed) + " door(s) connected to inaccessible rooms. Remaining doors: " + str(all_doors.size()), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Find the farthest reachable room (now all rooms are reachable, but find farthest)
|
||||
# Make sure we have at least 2 rooms (start and exit must be different)
|
||||
# exit_room_index is already declared at function level
|
||||
if all_rooms.size() < 2:
|
||||
push_error("DungeonGenerator: ERROR - Not enough reachable rooms! Need at least 2 (start + exit), but only have ", all_rooms.size())
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Not enough reachable rooms! Need at least 2 (start + exit), but only have " + str(all_rooms.size()), LogManager.CATEGORY_DUNGEON)
|
||||
# Use start room as exit if only one room exists (shouldn't happen, but handle gracefully)
|
||||
if all_rooms.size() == 1:
|
||||
exit_room_index = 0
|
||||
else:
|
||||
# No rooms at all - this is a critical error
|
||||
push_error("DungeonGenerator: CRITICAL ERROR - No rooms left after filtering!")
|
||||
LogManager.log_error("DungeonGenerator: CRITICAL ERROR - No rooms left after filtering!", LogManager.CATEGORY_DUNGEON)
|
||||
return {} # Return empty dungeon
|
||||
else:
|
||||
exit_room_index = _find_farthest_room(all_rooms, start_room_index)
|
||||
@@ -266,7 +266,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
exit_room_index = second_farthest
|
||||
|
||||
all_rooms[exit_room_index].modifiers.append({"type": "EXIT"})
|
||||
print("DungeonGenerator: Selected exit room at index ", exit_room_index, " position: ", all_rooms[exit_room_index].x, ",", all_rooms[exit_room_index].y)
|
||||
LogManager.log("DungeonGenerator: Selected exit room at index " + str(exit_room_index) + " position: " + str(all_rooms[exit_room_index].x) + "," + str(all_rooms[exit_room_index].y), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# 7. Render walls around rooms
|
||||
_render_room_walls(all_rooms, grid, tile_grid, map_size, rng)
|
||||
@@ -274,12 +274,12 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
# 7.5. Place stairs in exit room BEFORE placing torches (so torches don't overlap stairs)
|
||||
var stairs_data = _place_stairs_in_exit_room(all_rooms[exit_room_index], grid, tile_grid, map_size, all_doors, rng)
|
||||
if stairs_data.is_empty():
|
||||
print("DungeonGenerator: ERROR - Failed to place stairs in exit room! Room size: ", all_rooms[exit_room_index].w, "x", all_rooms[exit_room_index].h, " Doors: ", all_doors.size())
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Failed to place stairs in exit room! Room size: " + str(all_rooms[exit_room_index].w) + "x" + str(all_rooms[exit_room_index].h) + " Doors: " + str(all_doors.size()), LogManager.CATEGORY_DUNGEON)
|
||||
# CRITICAL: Force place stairs - we MUST have an exit!
|
||||
print("DungeonGenerator: FORCING stairs placement in exit room center")
|
||||
LogManager.log("DungeonGenerator: FORCING stairs placement in exit room center", LogManager.CATEGORY_DUNGEON)
|
||||
stairs_data = _force_place_stairs(all_rooms[exit_room_index], grid, tile_grid, map_size, all_doors, rng)
|
||||
if stairs_data.is_empty():
|
||||
push_error("DungeonGenerator: CRITICAL ERROR - Could not place stairs even with force placement!")
|
||||
LogManager.log_error("DungeonGenerator: CRITICAL ERROR - Could not place stairs even with force placement!", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# 8. Place torches in rooms (AFTER stairs, so torches don't overlap stairs)
|
||||
var all_torches = []
|
||||
@@ -309,7 +309,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
break
|
||||
if not already_in_list:
|
||||
rooms_with_spawner_puzzles.append(puzzle_room)
|
||||
print("DungeonGenerator: Room (", puzzle_room.x, ", ", puzzle_room.y, ") has monster spawner puzzle - will skip pre-spawning enemies")
|
||||
LogManager.log("DungeonGenerator: Room (" + str(puzzle_room.x) + ", " + str(puzzle_room.y) + ") has monster spawner puzzle - will skip pre-spawning enemies", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# 9. Place enemies in rooms (scaled by level, excluding start and exit rooms, and rooms with spawner puzzles)
|
||||
var all_enemies = []
|
||||
@@ -323,7 +323,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) -
|
||||
if spawner_room.x == room.x and spawner_room.y == room.y and \
|
||||
spawner_room.w == room.w and spawner_room.h == room.h:
|
||||
has_spawner_puzzle = true
|
||||
print("DungeonGenerator: Skipping pre-spawned enemies for room (", room.x, ", ", room.y, ") - has monster spawner puzzle")
|
||||
LogManager.log("DungeonGenerator: Skipping pre-spawned enemies for room (" + str(room.x) + ", " + str(room.y) + ") - has monster spawner puzzle", LogManager.CATEGORY_DUNGEON)
|
||||
break
|
||||
|
||||
if not has_spawner_puzzle:
|
||||
@@ -1317,7 +1317,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
# Choose a random wall to place stairs on (excluding corners)
|
||||
# Make sure stairs don't overlap any doors
|
||||
# Returns stairs data with position and size for Area2D creation
|
||||
print("DungeonGenerator: Placing stairs in exit room: ", exit_room.x, ",", exit_room.y, " size: ", exit_room.w, "x", exit_room.h, " doors: ", all_doors.size())
|
||||
LogManager.log("DungeonGenerator: Placing stairs in exit room: " + str(exit_room.x) + "," + str(exit_room.y) + " size: " + str(exit_room.w) + "x" + str(exit_room.h) + " doors: " + str(all_doors.size()), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
var stairs_data: Dictionary = {}
|
||||
var wall_choices = []
|
||||
@@ -1334,7 +1334,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
for stairs_tile_y in range(stairs_y, stairs_y + stairs_h):
|
||||
if stairs_tile_x >= 0 and stairs_tile_x < map_size.x and stairs_tile_y >= 0 and stairs_tile_y < map_size.y:
|
||||
if grid[stairs_tile_x][stairs_tile_y] == 2: # Grid value 2 = door
|
||||
print("DungeonGenerator: Stairs tile (", stairs_tile_x, ",", stairs_tile_y, ") is marked as door in grid!")
|
||||
LogManager.log("DungeonGenerator: Stairs tile (" + str(stairs_tile_x) + "," + str(stairs_tile_y) + ") is marked as door in grid!", LogManager.CATEGORY_DUNGEON)
|
||||
return true
|
||||
|
||||
# SECOND: Check door dictionary - verify against all known doors
|
||||
@@ -1377,7 +1377,8 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
# Check if this stairs tile matches any door tile
|
||||
for door_tile in door_tiles:
|
||||
if stairs_tile_x == door_tile.x and stairs_tile_y == door_tile.y:
|
||||
print("DungeonGenerator: Stairs tile (", stairs_tile_x, ",", stairs_tile_y, ") overlaps door tile (", door_tile.x, ",", door_tile.y, ") from door at (", door_x, ",", door_y, ") dir: ", door.dir if "dir" in door else "unknown")
|
||||
var door_dir_str = str(door.dir) if "dir" in door else "unknown"
|
||||
LogManager.log("DungeonGenerator: Stairs tile (" + str(stairs_tile_x) + "," + str(stairs_tile_y) + ") overlaps door tile (" + str(door_tile.x) + "," + str(door_tile.y) + ") from door at (" + str(door_x) + "," + str(door_y) + ") dir: " + door_dir_str, LogManager.CATEGORY_DUNGEON)
|
||||
return true
|
||||
return false
|
||||
|
||||
@@ -1453,7 +1454,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
})
|
||||
|
||||
if wall_choices.size() == 0:
|
||||
print("DungeonGenerator: ERROR - No valid walls for stairs! Exit room too small: ", exit_room.w, "x", exit_room.h)
|
||||
LogManager.log_error("DungeonGenerator: ERROR - No valid walls for stairs! Exit room too small: " + str(exit_room.w) + "x" + str(exit_room.h), LogManager.CATEGORY_DUNGEON)
|
||||
return {} # No valid walls for stairs
|
||||
|
||||
# Choose a random wall
|
||||
@@ -1462,7 +1463,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
if wall.dir == "UP" or wall.dir == "DOWN":
|
||||
# Horizontal stairs (3x2)
|
||||
if wall.x_range.size() == 0:
|
||||
print("DungeonGenerator: ERROR - x_range is empty for ", wall.dir, " stairs")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - x_range is empty for " + str(wall.dir) + " stairs", LogManager.CATEGORY_DUNGEON)
|
||||
return {}
|
||||
|
||||
# Try to find a position that doesn't overlap doors
|
||||
@@ -1473,7 +1474,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
valid_positions.append(test_x)
|
||||
|
||||
if valid_positions.size() == 0:
|
||||
print("DungeonGenerator: ERROR - No valid position found for ", wall.dir, " stairs (all positions overlap doors)")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - No valid position found for " + str(wall.dir) + " stairs (all positions overlap doors)", LogManager.CATEGORY_DUNGEON)
|
||||
# Don't allow stairs to overlap doors - this is a critical bug
|
||||
# Instead, try the next wall or return empty to force placement elsewhere
|
||||
return {} # No valid position found - will trigger _force_place_stairs
|
||||
@@ -1493,7 +1494,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
"world_size": Vector2(wall.w * tile_size, wall.h * tile_size)
|
||||
}
|
||||
|
||||
print("DungeonGenerator: Placed ", wall.dir, " stairs at tile (", stairs_start_x, ",", wall.y, ") world pos: ", stairs_data.world_pos, " in room (", exit_room.x, ",", exit_room.y, ") size ", exit_room.w, "x", exit_room.h)
|
||||
LogManager.log("DungeonGenerator: Placed " + str(wall.dir) + " stairs at tile (" + str(stairs_start_x) + "," + str(wall.y) + ") world pos: " + str(stairs_data.world_pos) + " in room (" + str(exit_room.x) + "," + str(exit_room.y) + ") size " + str(exit_room.w) + "x" + str(exit_room.h), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Mark grid cells as stairs (similar to doors)
|
||||
for dx in range(wall.w):
|
||||
@@ -1524,7 +1525,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
elif wall.dir == "LEFT" or wall.dir == "RIGHT":
|
||||
# Vertical stairs (2x3)
|
||||
if wall.y_range.size() == 0:
|
||||
print("DungeonGenerator: ERROR - y_range is empty for ", wall.dir, " stairs")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - y_range is empty for " + str(wall.dir) + " stairs", LogManager.CATEGORY_DUNGEON)
|
||||
return {}
|
||||
|
||||
# Try to find a position that doesn't overlap doors
|
||||
@@ -1535,7 +1536,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
valid_positions.append(test_y)
|
||||
|
||||
if valid_positions.size() == 0:
|
||||
print("DungeonGenerator: ERROR - No valid position found for ", wall.dir, " stairs (all positions overlap doors)")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - No valid position found for " + str(wall.dir) + " stairs (all positions overlap doors)", LogManager.CATEGORY_DUNGEON)
|
||||
# Don't allow stairs to overlap doors - this is a critical bug
|
||||
# Instead, try the next wall or return empty to force placement elsewhere
|
||||
return {} # No valid position found - will trigger _force_place_stairs
|
||||
@@ -1555,7 +1556,7 @@ func _place_stairs_in_exit_room(exit_room: Dictionary, grid: Array, tile_grid: A
|
||||
"world_size": Vector2(wall.w * tile_size, wall.h * tile_size)
|
||||
}
|
||||
|
||||
print("DungeonGenerator: Placed ", wall.dir, " stairs at tile (", wall.x, ",", stairs_start_y, ") world pos: ", stairs_data.world_pos, " in room (", exit_room.x, ",", exit_room.y, ") size ", exit_room.w, "x", exit_room.h)
|
||||
LogManager.log("DungeonGenerator: Placed " + str(wall.dir) + " stairs at tile (" + str(wall.x) + "," + str(stairs_start_y) + ") world pos: " + str(stairs_data.world_pos) + " in room (" + str(exit_room.x) + "," + str(exit_room.y) + ") size " + str(exit_room.w) + "x" + str(exit_room.h), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Mark grid cells as stairs
|
||||
for dx in range(wall.w):
|
||||
@@ -1588,7 +1589,7 @@ func _force_place_stairs(exit_room: Dictionary, grid: Array, tile_grid: Array, m
|
||||
# Force place stairs in exit room - used as fallback when normal placement fails
|
||||
# Still tries to avoid door overlaps, but will place stairs even if room is small
|
||||
# Uses same positioning logic as doors: at least 2 tiles from corners
|
||||
print("DungeonGenerator: Force placing stairs in exit room: ", exit_room.x, ",", exit_room.y, " size: ", exit_room.w, "x", exit_room.h)
|
||||
LogManager.log("DungeonGenerator: Force placing stairs in exit room: " + str(exit_room.x) + "," + str(exit_room.y) + " size: " + str(exit_room.w) + "x" + str(exit_room.h), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
var stairs_data: Dictionary = {}
|
||||
var tile_size = 16
|
||||
@@ -1747,7 +1748,7 @@ func _force_place_stairs(exit_room: Dictionary, grid: Array, tile_grid: Array, m
|
||||
|
||||
# If still no valid position found, return empty (don't place stairs that overlap doors!)
|
||||
if not found_position:
|
||||
print("DungeonGenerator: ERROR - Could not find any position for stairs that doesn't overlap doors!")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Could not find any position for stairs that doesn't overlap doors!", LogManager.CATEGORY_DUNGEON)
|
||||
return {}
|
||||
|
||||
stairs_data = {
|
||||
@@ -1807,7 +1808,7 @@ func _force_place_stairs(exit_room: Dictionary, grid: Array, tile_grid: Array, m
|
||||
# Fallback: use UP stairs tiles
|
||||
tile_grid[x][y] = STAIRS_UP_START + Vector2i(dx, dy)
|
||||
|
||||
print("DungeonGenerator: Force placed ", stairs_dir, " stairs at tile (", stairs_data.x, ",", stairs_data.y, ") world pos: ", stairs_data.world_pos)
|
||||
LogManager.log("DungeonGenerator: Force placed " + str(stairs_dir) + " stairs at tile (" + str(stairs_data.x) + "," + str(stairs_data.y) + ") world pos: " + str(stairs_data.world_pos), LogManager.CATEGORY_DUNGEON)
|
||||
return stairs_data
|
||||
|
||||
func _place_interactable_objects_in_room(room: Dictionary, grid: Array, map_size: Vector2i, all_doors: Array, all_enemies: Array, rng: RandomNumberGenerator, room_puzzle_data: Dictionary = {}) -> Array:
|
||||
@@ -1828,13 +1829,13 @@ func _place_interactable_objects_in_room(room: Dictionary, grid: Array, map_size
|
||||
if puzzle_room.x == room.x and puzzle_room.y == room.y and \
|
||||
puzzle_room.w == room.w and puzzle_room.h == room.h:
|
||||
var puzzle_info = room_puzzle_data[puzzle_room]
|
||||
print("DungeonGenerator: Checking room (", room.x, ",", room.y, ") - puzzle_room (", puzzle_room.x, ",", puzzle_room.y, ") puzzle_type: ", puzzle_info.type)
|
||||
LogManager.log("DungeonGenerator: Checking room (" + str(room.x) + "," + str(room.y) + ") - puzzle_room (" + str(puzzle_room.x) + "," + str(puzzle_room.y) + ") puzzle_type: " + str(puzzle_info.type), LogManager.CATEGORY_DUNGEON)
|
||||
if puzzle_info.type == "switch_pillar":
|
||||
has_pillar_switch_puzzle = true
|
||||
print("DungeonGenerator: Room (", room.x, ",", room.y, ") has pillar switch puzzle - will spawn at least 1 pillar")
|
||||
LogManager.log("DungeonGenerator: Room (" + str(room.x) + "," + str(room.y) + ") has pillar switch puzzle - will spawn at least 1 pillar", LogManager.CATEGORY_DUNGEON)
|
||||
break
|
||||
else:
|
||||
print("DungeonGenerator: room_puzzle_data is empty for room (", room.x, ",", room.y, ")")
|
||||
LogManager.log("DungeonGenerator: room_puzzle_data is empty for room (" + str(room.x) + "," + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Calculate room floor area (excluding walls)
|
||||
var floor_w = room.w - 4 # Excluding 2-tile walls on each side
|
||||
@@ -2090,7 +2091,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
|
||||
# STEP 1: For each room (except start/exit), randomly decide if it has a door-puzzle
|
||||
var puzzle_room_chance = 0.4 # 40% chance per room
|
||||
print("DungeonGenerator: Assigning puzzles to rooms (", all_rooms.size(), " total rooms, excluding start/exit)")
|
||||
LogManager.log("DungeonGenerator: Assigning puzzles to rooms (" + str(all_rooms.size()) + " total rooms, excluding start/exit)", LogManager.CATEGORY_DUNGEON)
|
||||
for i in range(all_rooms.size()):
|
||||
if i == start_room_index or i == exit_room_index:
|
||||
continue # Skip start and exit rooms
|
||||
@@ -2098,7 +2099,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
var room = all_rooms[i]
|
||||
|
||||
if rng.randf() < puzzle_room_chance:
|
||||
print("DungeonGenerator: Room (", room.x, ", ", room.y, ") selected for puzzle assignment")
|
||||
LogManager.log("DungeonGenerator: Room (" + str(room.x) + ", " + str(room.y) + ") selected for puzzle assignment", LogManager.CATEGORY_DUNGEON)
|
||||
# This room has a puzzle!
|
||||
# CRITICAL SAFETY CHECK: Never assign puzzles to start or exit rooms
|
||||
# Double-check even though we skip them in the loop
|
||||
@@ -2128,10 +2129,10 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
doors_in_room.append(door)
|
||||
|
||||
if doors_in_room.size() == 0:
|
||||
print("DungeonGenerator: Room (", room.x, ", ", room.y, ") has no doors connected - skipping puzzle assignment")
|
||||
LogManager.log("DungeonGenerator: Room (" + str(room.x) + ", " + str(room.y) + ") has no doors connected - skipping puzzle assignment", LogManager.CATEGORY_DUNGEON)
|
||||
continue # No doors connected to this room, skip
|
||||
|
||||
print("DungeonGenerator: Room (", room.x, ", ", room.y, ") has ", doors_in_room.size(), " doors - assigning puzzle")
|
||||
LogManager.log("DungeonGenerator: Room (" + str(room.x) + ", " + str(room.y) + ") has " + str(doors_in_room.size()) + " doors - assigning puzzle", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Decide puzzle type: 33% walk switch, 33% pillar switch, 33% enemy spawner (if room is large enough)
|
||||
var can_have_enemies = false
|
||||
@@ -2153,13 +2154,13 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
"type": puzzle_type,
|
||||
"doors": doors_in_room
|
||||
}
|
||||
print("DungeonGenerator: Stored puzzle data for room (", room.x, ", ", room.y, ") - type: ", puzzle_type, ", doors: ", doors_in_room.size())
|
||||
LogManager.log("DungeonGenerator: Stored puzzle data for room (" + str(room.x) + ", " + str(room.y) + ") - type: " + str(puzzle_type) + ", doors: " + str(doors_in_room.size()), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Mark these doors as assigned
|
||||
for door in doors_in_room:
|
||||
assigned_doors.append(door)
|
||||
|
||||
print("DungeonGenerator: Assigned puzzles to ", room_puzzle_data.size(), " rooms")
|
||||
LogManager.log("DungeonGenerator: Assigned puzzles to " + str(room_puzzle_data.size()) + " rooms", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# STEP 2: Create blocking doors for rooms with puzzles
|
||||
# CRITICAL: Blocking doors should ONLY be placed ON THE DOORS IN THE PUZZLE ROOM
|
||||
@@ -2168,7 +2169,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
for room in room_puzzle_data.keys():
|
||||
# CRITICAL SAFETY CHECK #1: Verify this room is actually in room_puzzle_data
|
||||
if not room in room_puzzle_data:
|
||||
push_error("DungeonGenerator: ERROR - Room (", room.x, ", ", room.y, ") is NOT in room_puzzle_data! This should never happen!")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Room (" + str(room.x) + ", " + str(room.y) + ") is NOT in room_puzzle_data! This should never happen!", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# CRITICAL SAFETY CHECK #2: Never create blocking doors for start or exit rooms
|
||||
@@ -2181,12 +2182,12 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
break
|
||||
|
||||
if room_index == start_room_index or room_index == exit_room_index:
|
||||
push_error("DungeonGenerator: ERROR - Attempted to create blocking doors for start/exit room! Skipping.")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Attempted to create blocking doors for start/exit room! Skipping.", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# CRITICAL SAFETY CHECK #3: Verify this room is actually in all_rooms (sanity check)
|
||||
if room_index == -1:
|
||||
push_error("DungeonGenerator: ERROR - Room (", room.x, ", ", room.y, ") not found in all_rooms! Skipping.")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Room (" + str(room.x) + ", " + str(room.y) + ") not found in all_rooms! Skipping.", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
var puzzle_info = room_puzzle_data[room]
|
||||
@@ -2194,7 +2195,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
var puzzle_type = puzzle_info.type
|
||||
|
||||
if doors_in_room.size() == 0:
|
||||
print("DungeonGenerator: WARNING - Room has puzzle but no doors! Skipping.")
|
||||
LogManager.log("DungeonGenerator: WARNING - Room has puzzle but no doors! Skipping.", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# Randomly choose door type: 50% StoneDoor, 50% GateDoor
|
||||
@@ -2219,9 +2220,9 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
"switch_data": switch_data,
|
||||
"switch_room": room
|
||||
}
|
||||
print("DungeonGenerator: Created switch puzzle for room (", room.x, ", ", room.y, ") - type: ", switch_type)
|
||||
LogManager.log("DungeonGenerator: Created switch puzzle for room (" + str(room.x) + ", " + str(room.y) + ") - type: " + str(switch_type), LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("DungeonGenerator: WARNING - Could not place floor switch in puzzle room (", room.x, ", ", room.y, ")! Skipping puzzle.")
|
||||
LogManager.log("DungeonGenerator: WARNING - Could not place floor switch in puzzle room (" + str(room.x) + ", " + str(room.y) + ")! Skipping puzzle.", LogManager.CATEGORY_DUNGEON)
|
||||
elif puzzle_type == "enemy":
|
||||
# Add enemy spawner IN THE PUZZLE ROOM
|
||||
var spawner_positions = []
|
||||
@@ -2246,13 +2247,13 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
"spawner_data": spawner_data,
|
||||
"spawner_room": room
|
||||
}
|
||||
print("DungeonGenerator: Created enemy spawner puzzle for room (", room.x, ", ", room.y, ") - spawner at ", spawner_data.position)
|
||||
LogManager.log("DungeonGenerator: Created enemy spawner puzzle for room (" + str(room.x) + ", " + str(room.y) + ") - spawner at " + str(spawner_data.position), LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("DungeonGenerator: WARNING - Could not place enemy spawner in puzzle room (", room.x, ", ", room.y, ")! Skipping puzzle.")
|
||||
LogManager.log("DungeonGenerator: WARNING - Could not place enemy spawner in puzzle room (" + str(room.x) + ", " + str(room.y) + ")! Skipping puzzle.", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# CRITICAL SAFETY CHECK: Only create blocking doors if puzzle element was successfully created
|
||||
if not puzzle_element_created:
|
||||
push_error("DungeonGenerator: ERROR - Puzzle element was NOT created for room (", room.x, ", ", room.y, ") with puzzle_type: ", puzzle_type, "! Skipping ALL doors in this room.")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Puzzle element was NOT created for room (" + str(room.x) + ", " + str(room.y) + ") with puzzle_type: " + str(puzzle_type) + "! Skipping ALL doors in this room.", LogManager.CATEGORY_DUNGEON)
|
||||
# Remove doors from assigned list since we're not creating the puzzle
|
||||
for door in doors_in_room:
|
||||
if door in assigned_doors:
|
||||
@@ -2261,12 +2262,12 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
|
||||
# CRITICAL: Verify puzzle_element_data is valid before proceeding
|
||||
if puzzle_element_data.is_empty() or not puzzle_element_data.has("type"):
|
||||
push_error("DungeonGenerator: ERROR - puzzle_element_data is invalid for room (", room.x, ", ", room.y, ")! puzzle_element_created was true but data is empty!")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - puzzle_element_data is invalid for room (" + str(room.x) + ", " + str(room.y) + ")! puzzle_element_created was true but data is empty!", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# Create blocking doors for at least 1 door (minimum), or all doors in the room
|
||||
# For now, create blocking doors for ALL doors in the puzzle room
|
||||
print("DungeonGenerator: Creating blocking doors for room (", room.x, ", ", room.y, ") with ", doors_in_room.size(), " doors, puzzle type: ", puzzle_type, ", puzzle_element type: ", puzzle_element_data.type)
|
||||
LogManager.log("DungeonGenerator: Creating blocking doors for room (" + str(room.x) + ", " + str(room.y) + ") with " + str(doors_in_room.size()) + " doors, puzzle type: " + str(puzzle_type) + ", puzzle_element type: " + str(puzzle_element_data.type), LogManager.CATEGORY_DUNGEON)
|
||||
for door in doors_in_room:
|
||||
# Determine direction based on which WALL of the PUZZLE ROOM the door is on
|
||||
var direction = _determine_door_direction_for_puzzle_room(door, room, all_rooms)
|
||||
@@ -2343,7 +2344,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
# Position is the OPEN state position (will move to CLOSED when entering room)
|
||||
# CRITICAL: Verify room is still a valid puzzle room before creating door
|
||||
if not room in room_puzzle_data:
|
||||
push_error("DungeonGenerator: ERROR - Room (", room.x, ", ", room.y, ") is no longer in room_puzzle_data! Cannot create door.")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Room (" + str(room.x) + ", " + str(room.y) + ") is no longer in room_puzzle_data! Cannot create door.", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# NOTE: door_room1 is already declared at line 1933 and verified to match puzzle room at line 1935-1940
|
||||
@@ -2364,7 +2365,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
# Store puzzle room as room1 for blocking doors
|
||||
door_data.original_room1 = room # Puzzle room is always room1 for blocking doors
|
||||
|
||||
print("DungeonGenerator: Creating blocking door for puzzle room (", room.x, ", ", room.y, ") - direction: ", direction, ", open_tile: (", open_tile_x, ",", open_tile_y, ")")
|
||||
LogManager.log("DungeonGenerator: Creating blocking door for puzzle room (" + str(room.x) + ", " + str(room.y) + ") - direction: " + str(direction) + ", open_tile: (" + str(open_tile_x) + "," + str(open_tile_y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# CRITICAL: Add puzzle-specific data from the puzzle_element_data created above (shared across all doors in room)
|
||||
# Only add door if puzzle element data is valid
|
||||
@@ -2379,7 +2380,7 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
door_data.switch_type = puzzle_element_data.switch_type
|
||||
door_data.switch_required_weight = puzzle_element_data.switch_weight
|
||||
door_has_valid_puzzle = true
|
||||
print("DungeonGenerator: Added switch data to door - switch at (", door_data.switch_tile_x, ", ", door_data.switch_tile_y, ")")
|
||||
LogManager.log("DungeonGenerator: Added switch data to door - switch at (" + str(door_data.switch_tile_x) + ", " + str(door_data.switch_tile_y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
elif puzzle_element_data.has("type") and puzzle_element_data.type == "enemy":
|
||||
if puzzle_element_data.has("spawner_data") and puzzle_element_data.spawner_data.has("position"):
|
||||
if not "enemy_spawners" in door_data:
|
||||
@@ -2393,11 +2394,11 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
})
|
||||
door_data.requires_enemies = true
|
||||
door_has_valid_puzzle = true
|
||||
print("DungeonGenerator: Added enemy spawner data to door - spawner at (", puzzle_element_data.spawner_data.tile_x, ", ", puzzle_element_data.spawner_data.tile_y, ")")
|
||||
LogManager.log("DungeonGenerator: Added enemy spawner data to door - spawner at (" + str(puzzle_element_data.spawner_data.tile_x) + ", " + str(puzzle_element_data.spawner_data.tile_y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# CRITICAL SAFETY CHECK: Only add door to blocking doors if it has valid puzzle element
|
||||
if not door_has_valid_puzzle:
|
||||
push_error("DungeonGenerator: ERROR - Blocking door for room (", room.x, ", ", room.y, ") has no valid puzzle element! Skipping door. puzzle_type: ", puzzle_type, ", puzzle_element_data: ", puzzle_element_data)
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Blocking door for room (" + str(room.x) + ", " + str(room.y) + ") has no valid puzzle element! Skipping door. puzzle_type: " + str(puzzle_type) + ", puzzle_element_data: " + str(puzzle_element_data), LogManager.CATEGORY_DUNGEON)
|
||||
continue # Skip this door - don't add it to blocking_doors
|
||||
|
||||
# FINAL SAFETY CHECK: Verify door has either requires_switch or requires_enemies set
|
||||
@@ -2405,18 +2406,18 @@ func _place_blocking_doors(all_rooms: Array, all_doors: Array, grid: Array, map_
|
||||
var has_switch = door_data.get("requires_switch", false) == true
|
||||
var has_enemies = door_data.get("requires_enemies", false) == true
|
||||
if not has_switch and not has_enemies:
|
||||
push_error("DungeonGenerator: ERROR - Blocking door (StoneDoor/GateDoor) has neither requires_switch nor requires_enemies! Door data: ", door_data.keys(), " - SKIPPING DOOR")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Blocking door (StoneDoor/GateDoor) has neither requires_switch nor requires_enemies! Door data: " + str(door_data.keys()) + " - SKIPPING DOOR", LogManager.CATEGORY_DUNGEON)
|
||||
continue # Skip this door - it's invalid
|
||||
|
||||
# FINAL CRITICAL SAFETY CHECK: Verify door's blocking_room matches the puzzle room exactly
|
||||
if door_data.blocking_room.x != room.x or door_data.blocking_room.y != room.y or \
|
||||
door_data.blocking_room.w != room.w or door_data.blocking_room.h != room.h:
|
||||
push_error("DungeonGenerator: ERROR - Door blocking_room (", door_data.blocking_room.x, ",", door_data.blocking_room.y, ") doesn't match puzzle room (", room.x, ",", room.y, ")! This door is for wrong room! SKIPPING DOOR")
|
||||
LogManager.log_error("DungeonGenerator: ERROR - Door blocking_room (" + str(door_data.blocking_room.x) + "," + str(door_data.blocking_room.y) + ") doesn't match puzzle room (" + str(room.x) + "," + str(room.y) + ")! This door is for wrong room! SKIPPING DOOR", LogManager.CATEGORY_DUNGEON)
|
||||
continue # Skip this door - it's for the wrong room
|
||||
|
||||
# Add door to blocking doors list ONLY if it has valid puzzle element
|
||||
blocking_doors.append(door_data)
|
||||
print("DungeonGenerator: Created blocking door for puzzle room (", room.x, ", ", room.y, ") - direction: ", direction, ", open tile: (", open_tile_x, ", ", open_tile_y, "), puzzle_type: ", puzzle_type, ", has_switch: ", door_data.get("requires_switch", false), ", has_enemies: ", door_data.get("requires_enemies", false))
|
||||
LogManager.log("DungeonGenerator: Created blocking door for puzzle room (" + str(room.x) + ", " + str(room.y) + ") - direction: " + str(direction) + ", open tile: (" + str(open_tile_x) + ", " + str(open_tile_y) + "), puzzle_type: " + str(puzzle_type) + ", has_switch: " + str(door_data.get("requires_switch", false)) + ", has_enemies: " + str(door_data.get("requires_enemies", false)), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# STEP 3: Randomly assign some doors as KeyDoors (except start/exit room doors and already assigned doors)
|
||||
var key_door_chance = 0.2 # 20% chance per door
|
||||
|
||||
@@ -142,10 +142,9 @@ func _physics_process(delta):
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_enemy_position"):
|
||||
# Send via game_world using enemy name/index and position for identification
|
||||
game_world._sync_enemy_position.rpc(enemy_name, enemy_index, position, velocity, position_z, current_direction, anim_frame, "", 0, state_val)
|
||||
else:
|
||||
# Fallback: try direct rpc (may fail if node path doesn't match)
|
||||
rpc("_sync_position", position, velocity, position_z, current_direction, anim_frame, "", 0, state_val)
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_position", [enemy_name, enemy_index, position, velocity, position_z, current_direction, anim_frame, "", 0, state_val])
|
||||
# Removed fallback rpc() call - it causes node path resolution errors
|
||||
# If game_world is not available, skip sync (will sync next frame)
|
||||
|
||||
func _ai_behavior(_delta):
|
||||
# Override in subclasses
|
||||
@@ -235,7 +234,7 @@ func _attack_player(player):
|
||||
# Fallback: broadcast if we can't get peer_id
|
||||
player.rpc_take_damage.rpc(damage, global_position)
|
||||
attack_timer = attack_cooldown
|
||||
print(name, " attacked ", player.name, " (peer: ", player_peer_id, ", server: ", multiplayer.get_unique_id(), ")")
|
||||
LogManager.log(str(name) + " attacked " + str(player.name) + " (peer: " + str(player_peer_id) + ", server: " + str(multiplayer.get_unique_id()) + ")", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
func _find_nearest_player() -> Node:
|
||||
var players = get_tree().get_nodes_in_group("player")
|
||||
@@ -306,7 +305,8 @@ func take_damage(amount: float, from_position: Vector2, is_critical: bool = fals
|
||||
var dodge_chance = character_stats.dodge_chance
|
||||
if dodge_roll < dodge_chance:
|
||||
_was_dodged = true
|
||||
print(name, " DODGED the attack! (DEX: ", character_stats.baseStats.dex + character_stats.get_pass("dex"), ", dodge chance: ", dodge_chance * 100.0, "%)")
|
||||
var dex_total = character_stats.baseStats.dex + character_stats.get_pass("dex")
|
||||
LogManager.log(str(name) + " DODGED the attack! (DEX: " + str(dex_total) + ", dodge chance: " + str(dodge_chance * 100.0) + "%)", LogManager.CATEGORY_ENEMY)
|
||||
# Show "DODGED" text
|
||||
_show_damage_number(0.0, from_position, false, false, true) # is_dodged = true
|
||||
# Sync dodge visual to clients (pass damage_amount=0.0 and is_dodged=true to indicate dodge)
|
||||
@@ -315,7 +315,7 @@ func take_damage(amount: float, from_position: Vector2, is_critical: bool = fals
|
||||
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_enemy_damage_visual"):
|
||||
game_world._sync_enemy_damage_visual.rpc(enemy_name, enemy_index, 0.0, from_position, false, true)
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_damage_visual", [enemy_name, enemy_index, 0.0, from_position, false, true])
|
||||
return # No damage taken, exit early
|
||||
|
||||
# If not dodged, apply damage with DEF reduction
|
||||
@@ -328,12 +328,12 @@ func take_damage(amount: float, from_position: Vector2, is_critical: bool = fals
|
||||
if character_stats.hp <= 0:
|
||||
character_stats.no_health.emit()
|
||||
var effective_def = character_stats.defense * (0.2 if is_critical else 1.0)
|
||||
print(name, " took ", actual_damage, " damage (", amount, " base - ", effective_def, " DEF = ", actual_damage, ")! Health: ", current_health, "/", character_stats.maxhp)
|
||||
LogManager.log(str(name) + " took " + str(actual_damage) + " damage (" + str(amount) + " base - " + str(effective_def) + " DEF = " + str(actual_damage) + ")! Health: " + str(current_health) + "/" + str(character_stats.maxhp), LogManager.CATEGORY_ENEMY)
|
||||
else:
|
||||
# Fallback for legacy (shouldn't happen if _initialize_character_stats is called)
|
||||
current_health -= amount
|
||||
actual_damage = amount
|
||||
print(name, " took ", amount, " damage! Health: ", current_health, " (critical: ", is_critical, ")")
|
||||
LogManager.log(str(name) + " took " + str(amount) + " damage! Health: " + str(current_health) + " (critical: " + str(is_critical) + ")", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Calculate knockback direction (away from attacker)
|
||||
var knockback_direction = (global_position - from_position).normalized()
|
||||
@@ -357,7 +357,7 @@ func take_damage(amount: float, from_position: Vector2, is_critical: bool = fals
|
||||
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_enemy_damage_visual"):
|
||||
game_world._sync_enemy_damage_visual.rpc(enemy_name, enemy_index, actual_damage, from_position, is_critical)
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_damage_visual", [enemy_name, enemy_index, actual_damage, from_position, is_critical])
|
||||
else:
|
||||
# Fallback: try direct RPC (may fail if node path doesn't match)
|
||||
_sync_damage_visual.rpc(actual_damage, from_position, is_critical)
|
||||
@@ -467,7 +467,7 @@ func _notify_doors_enemy_died():
|
||||
if door.requires_enemies:
|
||||
# Trigger puzzle state check immediately (doors will verify if all enemies are dead)
|
||||
door.call_deferred("_check_puzzle_state")
|
||||
print("Enemy: Notified door ", door.name, " to check puzzle state after enemy death")
|
||||
LogManager.log("Enemy: Notified door " + str(door.name) + " to check puzzle state after enemy death", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
func _set_animation(_anim_name: String):
|
||||
# Virtual function - override in subclasses that use animation state system
|
||||
@@ -479,17 +479,26 @@ func _die():
|
||||
return
|
||||
|
||||
is_dead = true
|
||||
print(name, " died!")
|
||||
LogManager.log(str(name) + " died!", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Track defeated enemy for syncing to new clients
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
|
||||
if enemy_index >= 0:
|
||||
game_world.defeated_enemies[enemy_index] = true
|
||||
LogManager.log("Enemy: Tracked defeated enemy with index " + str(enemy_index), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Credit kill and grant EXP to the player who dealt the fatal damage
|
||||
if killer_player and is_instance_valid(killer_player) and killer_player.character_stats:
|
||||
killer_player.character_stats.kills += 1
|
||||
print(name, " kill credited to ", killer_player.name, " (total kills: ", killer_player.character_stats.kills, ")")
|
||||
LogManager.log(str(name) + " kill credited to " + str(killer_player.name) + " (total kills: " + str(killer_player.character_stats.kills) + ")", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Grant EXP to the killer
|
||||
if exp_reward > 0:
|
||||
killer_player.character_stats.add_xp(exp_reward)
|
||||
print(name, " granted ", exp_reward, " EXP to ", killer_player.name)
|
||||
LogManager.log(str(name) + " granted " + str(exp_reward) + " EXP to " + str(killer_player.name), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Sync kill update to client if this player belongs to a client
|
||||
# Only sync if we're on the server and the killer is a client's player
|
||||
@@ -499,7 +508,7 @@ func _die():
|
||||
if killer_peer_id != 0 and killer_peer_id != multiplayer.get_unique_id() and killer_player.has_method("_sync_stats_update"):
|
||||
# Server is updating a client's player stats - sync to the client
|
||||
var coins = killer_player.character_stats.coin if "coin" in killer_player.character_stats else 0
|
||||
print(name, " syncing kill stats to client peer_id=", killer_peer_id, " kills=", killer_player.character_stats.kills, " coins=", coins)
|
||||
LogManager.log(str(name) + " syncing kill stats to client peer_id=" + str(killer_peer_id) + " kills=" + str(killer_player.character_stats.kills) + " coins=" + str(coins), LogManager.CATEGORY_ENEMY)
|
||||
killer_player._sync_stats_update.rpc_id(killer_peer_id, killer_player.character_stats.kills, coins)
|
||||
|
||||
# Spawn loot immediately (before death animation)
|
||||
@@ -512,7 +521,7 @@ func _die():
|
||||
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_enemy_death"):
|
||||
game_world._sync_enemy_death.rpc(enemy_name, enemy_index)
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_death", [enemy_name, enemy_index])
|
||||
else:
|
||||
# Fallback: try direct RPC (may fail if node path doesn't match)
|
||||
_sync_death.rpc()
|
||||
@@ -528,20 +537,20 @@ func _play_death_animation():
|
||||
func _spawn_loot():
|
||||
# Only spawn loot on server/authority
|
||||
if not is_multiplayer_authority():
|
||||
print(name, " _spawn_loot() called but not authority, skipping")
|
||||
LogManager.log(str(name) + " _spawn_loot() called but not authority, skipping", LogManager.CATEGORY_ENEMY)
|
||||
return
|
||||
|
||||
print(name, " _spawn_loot() called on authority")
|
||||
LogManager.log(str(name) + " _spawn_loot() called on authority", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Spawn random loot at enemy position
|
||||
var loot_scene = preload("res://scenes/loot.tscn")
|
||||
if not loot_scene:
|
||||
print(name, " ERROR: loot_scene is null!")
|
||||
LogManager.log_error(str(name) + " ERROR: loot_scene is null!", LogManager.CATEGORY_ENEMY)
|
||||
return
|
||||
|
||||
# Random chance to drop loot (70% chance)
|
||||
var loot_chance = randf()
|
||||
print(name, " loot chance roll: ", loot_chance, " (need > 0.3)")
|
||||
LogManager.log(str(name) + " loot chance roll: " + str(loot_chance) + " (need > 0.3)", LogManager.CATEGORY_ENEMY)
|
||||
if loot_chance > 0.3:
|
||||
# Decide what to drop: 30% coin, 30% food, 40% item
|
||||
var drop_roll = randf()
|
||||
@@ -575,7 +584,7 @@ func _spawn_loot():
|
||||
|
||||
var entities_node = get_parent()
|
||||
if not entities_node:
|
||||
print(name, " ERROR: entities_node is null! Cannot spawn loot!")
|
||||
LogManager.log_error(str(name) + " ERROR: entities_node is null! Cannot spawn loot!", LogManager.CATEGORY_ENEMY)
|
||||
return
|
||||
|
||||
if drop_item:
|
||||
@@ -583,7 +592,7 @@ func _spawn_loot():
|
||||
var item = ItemDatabase.get_random_enemy_drop()
|
||||
if item:
|
||||
ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world)
|
||||
print(name, " ✓ dropped item: ", item.item_name, " at ", safe_spawn_pos)
|
||||
LogManager.log(str(name) + " ✓ dropped item: " + str(item.item_name) + " at " + str(safe_spawn_pos), LogManager.CATEGORY_ENEMY)
|
||||
else:
|
||||
# Spawn regular loot (coin or food)
|
||||
var loot = loot_scene.instantiate()
|
||||
@@ -595,7 +604,7 @@ func _spawn_loot():
|
||||
loot.velocity_z = random_velocity_z
|
||||
loot.velocity_set_by_spawner = true
|
||||
loot.is_airborne = true
|
||||
print(name, " ✓ dropped loot: ", loot_type, " at ", safe_spawn_pos, " (original enemy pos: ", global_position, ")")
|
||||
LogManager.log(str(name) + " ✓ dropped loot: " + str(loot_type) + " at " + str(safe_spawn_pos) + " (original enemy pos: " + str(global_position) + ")", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Sync loot spawn to all clients (use safe position)
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
@@ -608,12 +617,12 @@ func _spawn_loot():
|
||||
# Store loot ID on server loot instance
|
||||
loot.set_meta("loot_id", loot_id)
|
||||
# Sync to clients with ID
|
||||
game_world._sync_loot_spawn.rpc(safe_spawn_pos, loot_type, initial_velocity, random_velocity_z, loot_id)
|
||||
print(name, " ✓ synced loot spawn to clients")
|
||||
game_world._rpc_to_ready_peers("_sync_loot_spawn", [safe_spawn_pos, loot_type, initial_velocity, random_velocity_z, loot_id])
|
||||
LogManager.log(str(name) + " ✓ synced loot spawn to clients", LogManager.CATEGORY_ENEMY)
|
||||
else:
|
||||
print(name, " ERROR: game_world not found for loot sync!")
|
||||
LogManager.log_error(str(name) + " ERROR: game_world not found for loot sync!", LogManager.CATEGORY_ENEMY)
|
||||
else:
|
||||
print(name, " loot chance failed (", loot_chance, " <= 0.3), no loot dropped")
|
||||
LogManager.log(str(name) + " loot chance failed (" + str(loot_chance) + " <= 0.3), no loot dropped", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# This function can be called directly (not just via RPC) when game_world routes the update
|
||||
func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0, frame: int = 0, anim: String = "", frame_num: int = 0, state_value: int = -1):
|
||||
@@ -625,12 +634,12 @@ func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0
|
||||
# Debug: Log when client receives position update (first few times)
|
||||
if not has_meta("position_sync_count"):
|
||||
set_meta("position_sync_count", 0)
|
||||
print("Enemy ", name, " (client) RECEIVED first position sync! pos=", pos, " authority: ", get_multiplayer_authority(), " is_authority: ", is_multiplayer_authority(), " in_tree: ", is_inside_tree())
|
||||
LogManager.log("Enemy " + str(name) + " (client) RECEIVED first position sync! pos=" + str(pos) + " authority: " + str(get_multiplayer_authority()) + " is_authority: " + str(is_multiplayer_authority()) + " in_tree: " + str(is_inside_tree()), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
var sync_count = get_meta("position_sync_count") + 1
|
||||
set_meta("position_sync_count", sync_count)
|
||||
if sync_count <= 3: # Log first 3 syncs
|
||||
print("Enemy ", name, " (client) received position sync #", sync_count, ": pos=", pos)
|
||||
LogManager.log("Enemy " + str(name) + " (client) received position sync #" + str(sync_count) + ": pos=" + str(pos), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Update position and state
|
||||
position = pos
|
||||
@@ -661,6 +670,10 @@ func _sync_damage_visual(damage_amount: float = 0.0, attacker_position: Vector2
|
||||
if is_multiplayer_authority():
|
||||
return # Server ignores its own updates
|
||||
|
||||
# Trigger damage animation and state change on client
|
||||
# This ensures clients play the damage animation (e.g., slime DAMAGE animation)
|
||||
_on_take_damage()
|
||||
|
||||
_flash_damage()
|
||||
|
||||
# Show damage number on client (even if damage_amount is 0 for dodges/misses)
|
||||
@@ -676,7 +689,7 @@ func _sync_death():
|
||||
|
||||
if not is_dead:
|
||||
is_dead = true
|
||||
print(name, " received death sync, dying on client")
|
||||
LogManager.log(str(name) + " received death sync, dying on client", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Remove collision layer so they don't collide with players, but still collide with walls
|
||||
# This matches what happens on the server when rats/slimes die
|
||||
@@ -687,7 +700,7 @@ func _sync_death():
|
||||
_play_death_animation()
|
||||
else:
|
||||
# Already dead, but make sure collision is removed and it's removed from scene
|
||||
print(name, " received death sync but already dead, ensuring removal")
|
||||
LogManager.log(str(name) + " received death sync but already dead, ensuring removal", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Remove collision layer if not already removed
|
||||
if get_collision_layer_value(2):
|
||||
|
||||
@@ -200,11 +200,11 @@ func _ready():
|
||||
# But still deterministic across clients by using a synced random value
|
||||
var random_component = randi() # This will be different each spawn
|
||||
seed_value = hash(str(spawn_position) + str(humanoid_type) + str(random_component))
|
||||
print(name, " appearance seed (randomized): ", seed_value, " at spawn position: ", spawn_position, " type: ", humanoid_type)
|
||||
LogManager.log(str(name) + " appearance seed (randomized): " + str(seed_value) + " at spawn position: " + str(spawn_position) + " type: " + str(humanoid_type), LogManager.CATEGORY_ENEMY)
|
||||
else:
|
||||
# Deterministic based on position and type only
|
||||
seed_value = hash(str(spawn_position) + str(humanoid_type))
|
||||
print(name, " appearance seed (deterministic): ", seed_value, " at spawn position: ", spawn_position, " type: ", humanoid_type)
|
||||
LogManager.log(str(name) + " appearance seed (deterministic): " + str(seed_value) + " at spawn position: " + str(spawn_position) + " type: " + str(humanoid_type), LogManager.CATEGORY_ENEMY)
|
||||
appearance_rng.seed = seed_value
|
||||
|
||||
# Set up appearance based on type
|
||||
@@ -678,7 +678,7 @@ func _load_type_addons():
|
||||
sprite_addons.texture = texture
|
||||
sprite_addons.hframes = 35
|
||||
sprite_addons.vframes = 8
|
||||
print(name, " loaded type addon: ", addon_path)
|
||||
LogManager.log(str(name) + " loaded type addon: " + str(addon_path), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
HumanoidType.SKELETON:
|
||||
# Can have (but not must) skeleton horns
|
||||
@@ -690,7 +690,7 @@ func _load_type_addons():
|
||||
sprite_addons.texture = texture
|
||||
sprite_addons.hframes = 35
|
||||
sprite_addons.vframes = 8
|
||||
print(name, " loaded skeleton horns: ", addon_path)
|
||||
LogManager.log(str(name) + " loaded skeleton horns: " + str(addon_path), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
HumanoidType.HUMANOID:
|
||||
# Ears are already set by _randomize_appearance, so skip here
|
||||
@@ -706,7 +706,7 @@ func _load_type_addons():
|
||||
sprite_addons.texture = texture
|
||||
sprite_addons.hframes = 35
|
||||
sprite_addons.vframes = 8
|
||||
print(name, " loaded night elf ears")
|
||||
LogManager.log(str(name) + " loaded night elf ears", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
HumanoidType.DEMON:
|
||||
# Can have DemonEars or DemonJaw
|
||||
@@ -718,7 +718,7 @@ func _load_type_addons():
|
||||
sprite_addons.texture = texture
|
||||
sprite_addons.hframes = 35
|
||||
sprite_addons.vframes = 8
|
||||
print(name, " loaded demon addon: ", addon_path)
|
||||
LogManager.log(str(name) + " loaded demon addon: " + str(addon_path), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
func _load_beastkin_addon():
|
||||
# Load random beastkin addon (low chance, can override type addon)
|
||||
@@ -734,7 +734,7 @@ func _load_beastkin_addon():
|
||||
sprite_addons.texture = texture
|
||||
sprite_addons.hframes = 35
|
||||
sprite_addons.vframes = 8
|
||||
print(name, " loaded beastkin addon: ", addon_path)
|
||||
LogManager.log(str(name) + " loaded beastkin addon: " + str(addon_path), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
func _setup_stats():
|
||||
# Set stats based on type
|
||||
@@ -796,7 +796,7 @@ func _setup_stats():
|
||||
charge_multiplier = clamp(charge_multiplier, 0.375, 1.0) # Clamp between 0.375x (2.67x faster) and 1.0x
|
||||
base_attack_charge_time = 0.4 * charge_multiplier
|
||||
|
||||
print(name, " stats: DEX=", dex, " attack_cooldown=", attack_cooldown, " charge_time=", base_attack_charge_time)
|
||||
LogManager.log(str(name) + " stats: DEX=" + str(dex) + " attack_cooldown=" + str(attack_cooldown) + " charge_time=" + str(base_attack_charge_time), LogManager.CATEGORY_ENEMY)
|
||||
|
||||
# Setup alert indicators
|
||||
if alert_indicator:
|
||||
@@ -874,7 +874,7 @@ func _physics_process(delta):
|
||||
# Send via game_world using enemy name/index and position for identification
|
||||
var enemy_name = name
|
||||
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
|
||||
game_world._sync_enemy_position.rpc(enemy_name, enemy_index, position, velocity, position_z, current_direction, 0, current_animation, current_frame, -1)
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_position", [enemy_name, enemy_index, position, velocity, position_z, current_direction, 0, current_animation, current_frame, -1])
|
||||
else:
|
||||
# Fallback: try direct call to _sync_position (not RPC)
|
||||
_sync_position(position, velocity, position_z, current_direction, 0, current_animation, current_frame)
|
||||
@@ -1188,7 +1188,13 @@ func _perform_attack():
|
||||
|
||||
# Sync attack animation to clients first
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority():
|
||||
_sync_attack.rpc(current_direction, attack_direction)
|
||||
# Use game_world to send RPC instead of rpc() on node instance
|
||||
# This avoids node path resolution issues when clients haven't spawned yet
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var enemy_name = name
|
||||
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
|
||||
if game_world and game_world.has_method("_sync_enemy_attack"):
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_attack", [enemy_name, enemy_index, current_direction, attack_direction])
|
||||
|
||||
# Delay before spawning sword slash
|
||||
await get_tree().create_timer(0.15).timeout
|
||||
@@ -1207,7 +1213,7 @@ func _perform_attack():
|
||||
if parent:
|
||||
parent.add_child(projectile)
|
||||
else:
|
||||
push_error("EnemyHumanoid: ERROR - No parent node to add projectile to!")
|
||||
LogManager.log_error("EnemyHumanoid: ERROR - No parent node to add projectile to!", LogManager.CATEGORY_ENEMY)
|
||||
projectile.queue_free()
|
||||
|
||||
func _try_attack_object(obj: Node):
|
||||
@@ -1231,11 +1237,11 @@ func _try_attack_object(obj: Node):
|
||||
# Perform attack - sword projectile will damage the object if it hits
|
||||
# The object will handle damage from sword projectiles (sword_projectile.gd already handles this)
|
||||
_perform_attack()
|
||||
print(name, " is attacking object ", obj.name, "!")
|
||||
LogManager.log(str(name) + " is attacking object " + str(obj.name) + "!", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_attack(direction: int, attack_dir: Vector2):
|
||||
# Sync attack to clients
|
||||
# Sync attack to clients (called by game_world, not via RPC)
|
||||
# Only process on clients (not authority)
|
||||
if not is_multiplayer_authority():
|
||||
current_direction = direction as Direction
|
||||
_set_animation("SWORD")
|
||||
@@ -1248,7 +1254,7 @@ func _sync_attack(direction: int, attack_dir: Vector2):
|
||||
projectile.setup(attack_dir, self)
|
||||
var spawn_offset = attack_dir * 10.0
|
||||
projectile.global_position = global_position + spawn_offset
|
||||
print(name, " performed synced attack!")
|
||||
LogManager.log(str(name) + " performed synced attack!", LogManager.CATEGORY_ENEMY)
|
||||
|
||||
func _set_animation(anim_name: String):
|
||||
if anim_name in ANIMATIONS:
|
||||
|
||||
@@ -268,6 +268,19 @@ func _update_client_visuals():
|
||||
# Update visuals on clients based on synced state
|
||||
super._update_client_visuals()
|
||||
|
||||
# Map synced state to animation (similar to how bat/rat use state directly)
|
||||
match state:
|
||||
SlimeState.IDLE:
|
||||
_set_animation("IDLE")
|
||||
SlimeState.MOVING:
|
||||
_set_animation("MOVE")
|
||||
SlimeState.JUMPING:
|
||||
_set_animation("JUMP")
|
||||
SlimeState.DAMAGED:
|
||||
_set_animation("DAMAGE")
|
||||
SlimeState.DYING:
|
||||
_set_animation("DIE")
|
||||
|
||||
# Update animation based on synced state
|
||||
_update_animation(0.0) # Update animation immediately when state changes
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ func spawn_enemy():
|
||||
node = node.get_parent()
|
||||
|
||||
if game_world and game_world.has_method("_sync_smoke_puffs"):
|
||||
game_world._sync_smoke_puffs.rpc(name, puff_positions)
|
||||
game_world._rpc_to_ready_peers("_sync_smoke_puffs", [name, puff_positions])
|
||||
|
||||
# Wait for smoke puffs to finish animating before spawning enemy
|
||||
# Reduced duration for faster spawning: 4 frames * (1/10.0) seconds per frame = 0.4s, plus move_duration 1.0s, plus fade_duration 0.3s = ~1.7s total
|
||||
@@ -239,7 +239,7 @@ func spawn_enemy():
|
||||
# Pass humanoid_type if it's a humanoid enemy (for syncing to clients)
|
||||
var sync_humanoid_type = humanoid_type if humanoid_type != null else -1
|
||||
print(" DEBUG: Calling _sync_enemy_spawn.rpc with name=", name, " pos=", global_position, " scene_index=", scene_index, " humanoid_type=", sync_humanoid_type)
|
||||
game_world._sync_enemy_spawn.rpc(name, global_position, scene_index, sync_humanoid_type)
|
||||
game_world._rpc_to_ready_peers("_sync_enemy_spawn", [name, global_position, scene_index, sync_humanoid_type])
|
||||
print(" Sent RPC to sync enemy spawn to clients: spawner=", name, " pos=", global_position, " scene_index=", scene_index, " humanoid_type=", sync_humanoid_type)
|
||||
else:
|
||||
var has_method_str = str(game_world.has_method("_sync_enemy_spawn")) if game_world else "N/A"
|
||||
|
||||
@@ -186,6 +186,13 @@ func _check_activation():
|
||||
is_activated = should_activate
|
||||
_update_visual()
|
||||
|
||||
# Track activated switch for syncing to new clients
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server() and is_activated:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
game_world.activated_switches[name] = true
|
||||
LogManager.log("FloorSwitch: Tracked activated switch " + name, LogManager.CATEGORY_NETWORK)
|
||||
|
||||
# Notify connected doors
|
||||
_notify_doors()
|
||||
|
||||
|
||||
@@ -9,18 +9,33 @@ extends CanvasLayer
|
||||
@onready var network_mode_container = $Control/MainMenu/VBoxContainer/NetworkModeContainer
|
||||
@onready var local_players_spinbox = $Control/MainMenu/VBoxContainer/LocalPlayersContainer/SpinBox
|
||||
@onready var address_input = $Control/MainMenu/VBoxContainer/AddressContainer/AddressInput
|
||||
@onready var room_fetch_status_container = $Control/MainMenu/VBoxContainer/RoomFetchStatusContainer
|
||||
@onready var loading_label = $Control/MainMenu/VBoxContainer/RoomFetchStatusContainer/RoomFetchLoadingContainer/LoadingLabel
|
||||
@onready var loading_spinner = $Control/MainMenu/VBoxContainer/RoomFetchStatusContainer/RoomFetchLoadingContainer/LoadingSpinner
|
||||
@onready var last_fetch_label = $Control/MainMenu/VBoxContainer/RoomFetchStatusContainer/LastFetchLabel
|
||||
|
||||
@onready var network_manager = $"/root/NetworkManager"
|
||||
|
||||
var connection_error_label: Label = null
|
||||
var connection_error_shown: bool = false # Prevent spamming error messages
|
||||
var is_joining_attempt: bool = false
|
||||
var last_join_address: String = ""
|
||||
var room_fetch_timer: Timer = null # Timer for retrying room fetches
|
||||
var is_auto_joining: bool = false # Track if we're in auto-join mode
|
||||
var is_hosting: bool = false # Track if we're hosting (don't fetch rooms when hosting)
|
||||
var room_list_container: VBoxContainer = null # Container for displaying available rooms
|
||||
var refresh_button: Button = null # Refresh button for manually reloading rooms
|
||||
var refresh_cooldown_timer: Timer = null # Timer for refresh button cooldown
|
||||
|
||||
func _ready():
|
||||
# Wait for nodes to be ready
|
||||
await get_tree().process_frame
|
||||
|
||||
# Debug: Print node paths
|
||||
print("GameUI _ready() called")
|
||||
print("Main menu node: ", main_menu)
|
||||
print("Host button node: ", host_button)
|
||||
print("Join button node: ", join_button)
|
||||
# Debug: Print node paths (UI category - disabled by default for network debugging)
|
||||
LogManager.log("GameUI _ready() called", LogManager.CATEGORY_UI)
|
||||
LogManager.log("Main menu node: " + str(main_menu), LogManager.CATEGORY_UI)
|
||||
LogManager.log("Host button node: " + str(host_button), LogManager.CATEGORY_UI)
|
||||
LogManager.log("Join button node: " + str(join_button), LogManager.CATEGORY_UI)
|
||||
|
||||
# Verify nodes exist
|
||||
if not host_button:
|
||||
@@ -40,7 +55,7 @@ func _ready():
|
||||
|
||||
# On web builds, filter out ENet option (only WebRTC and WebSocket available)
|
||||
if OS.get_name() == "Web":
|
||||
print("GameUI: Web platform detected, filtering network mode options")
|
||||
LogManager.log("GameUI: Web platform detected, filtering network mode options", LogManager.CATEGORY_UI)
|
||||
# Remove ENet option (index 0) for web builds
|
||||
network_mode_option.remove_item(0)
|
||||
# Adjust selected index (was 0 for ENet, now 0 is WebRTC)
|
||||
@@ -59,43 +74,52 @@ func _ready():
|
||||
if network_manager:
|
||||
network_manager.connection_succeeded.connect(_on_connection_succeeded)
|
||||
network_manager.connection_failed.connect(_on_connection_failed)
|
||||
# Connect to rooms_fetched for displaying rooms (non-auto-join mode)
|
||||
if not network_manager.rooms_fetched.is_connected(_on_rooms_fetched_display):
|
||||
network_manager.rooms_fetched.connect(_on_rooms_fetched_display)
|
||||
else:
|
||||
push_error("NetworkManager not found!")
|
||||
|
||||
# Check for command-line arguments
|
||||
_check_command_line_args()
|
||||
|
||||
# If WebRTC is selected at startup (not auto-joining and not hosting), fetch rooms
|
||||
if not is_auto_joining and not is_hosting:
|
||||
var current_mode = network_manager.network_mode
|
||||
if current_mode == 1: # WebRTC
|
||||
_start_room_fetch()
|
||||
|
||||
func _check_command_line_args():
|
||||
var args = OS.get_cmdline_args()
|
||||
print("GameUI: Parsing command-line arguments: ", args)
|
||||
LogManager.log("GameUI: Parsing command-line arguments: " + str(args), LogManager.CATEGORY_UI)
|
||||
|
||||
# Parse arguments
|
||||
var should_host = false
|
||||
var should_join = false
|
||||
var should_debug = false
|
||||
var force_webrtc = false
|
||||
var join_address = "ruinborn.thefirstboss.com"
|
||||
var join_address = "" # Empty by default - will fetch rooms if WebRTC/WebSocket
|
||||
var local_count = 1
|
||||
|
||||
for arg in args:
|
||||
if arg == "--host":
|
||||
should_host = true
|
||||
print("GameUI: Found --host argument")
|
||||
LogManager.log("GameUI: Found --host argument", LogManager.CATEGORY_UI)
|
||||
elif arg == "--join":
|
||||
should_join = true
|
||||
print("GameUI: Found --join argument")
|
||||
LogManager.log("GameUI: Found --join argument", LogManager.CATEGORY_UI)
|
||||
elif arg == "--websocket" or arg == "--webrtc":
|
||||
force_webrtc = true
|
||||
print("GameUI: Found --websocket/--webrtc argument (forcing WebSocket mode)")
|
||||
LogManager.log("GameUI: Found --websocket/--webrtc argument (forcing WebSocket mode)", LogManager.CATEGORY_UI)
|
||||
elif arg == "--room-debug":
|
||||
should_debug = true
|
||||
print("GameUI: Found --room-debug argument")
|
||||
LogManager.log("GameUI: Found --room-debug argument", LogManager.CATEGORY_UI)
|
||||
elif arg.begins_with("--address="):
|
||||
join_address = arg.split("=")[1]
|
||||
elif arg.begins_with("--players="):
|
||||
local_count = int(arg.split("=")[1])
|
||||
|
||||
print("GameUI: Parsed flags - should_host: ", should_host, ", should_join: ", should_join, ", force_webrtc: ", force_webrtc, ", should_debug: ", should_debug)
|
||||
LogManager.log("GameUI: Parsed flags - should_host: " + str(should_host) + ", should_join: " + str(should_join) + ", force_webrtc: " + str(force_webrtc) + ", should_debug: " + str(should_debug), LogManager.CATEGORY_UI)
|
||||
|
||||
# Force WebRTC mode if --webrtc flag is present
|
||||
if force_webrtc:
|
||||
@@ -106,28 +130,368 @@ func _check_command_line_args():
|
||||
else:
|
||||
network_mode_option.selected = 1 # WebRTC is second option on native
|
||||
_on_network_mode_changed(network_mode_option.selected)
|
||||
print("GameUI: WebRTC mode forced via --webrtc flag")
|
||||
LogManager.log("GameUI: WebRTC mode forced via --webrtc flag", LogManager.CATEGORY_UI)
|
||||
|
||||
# Set debug flag only if --room-debug is used with --host or --join
|
||||
if should_debug and (should_host or should_join):
|
||||
network_manager.show_room_labels = true
|
||||
print("Debug mode enabled: room labels will be shown")
|
||||
LogManager.log("Debug mode enabled: room labels will be shown", LogManager.CATEGORY_UI)
|
||||
else:
|
||||
print("GameUI: Debug mode NOT enabled - should_debug: ", should_debug, ", should_host: ", should_host, ", should_join: ", should_join)
|
||||
LogManager.log("GameUI: Debug mode NOT enabled - should_debug: " + str(should_debug) + ", should_host: " + str(should_host) + ", should_join: " + str(should_join), LogManager.CATEGORY_UI)
|
||||
|
||||
# Auto-start based on arguments
|
||||
if should_host:
|
||||
print("Auto-hosting due to --host argument")
|
||||
is_hosting = true # Set flag so we don't fetch rooms
|
||||
LogManager.log("Auto-hosting due to --host argument", LogManager.CATEGORY_UI)
|
||||
network_manager.set_local_player_count(local_count)
|
||||
if network_manager.host_game():
|
||||
_start_game()
|
||||
elif should_join:
|
||||
print("Auto-joining to ", join_address, " due to --join argument")
|
||||
address_input.text = join_address
|
||||
network_manager.set_local_player_count(local_count)
|
||||
if network_manager.join_game(join_address):
|
||||
# Connection callback will handle starting the game
|
||||
pass
|
||||
# Check network mode after it's been set
|
||||
var current_mode = network_manager.network_mode
|
||||
if join_address.is_empty() and (current_mode == 1 or current_mode == 2):
|
||||
# No address provided, and using WebRTC or WebSocket - fetch and auto-join first available room
|
||||
LogManager.log("Auto-joining: No address provided, fetching available rooms (mode: " + str(current_mode) + ")...", LogManager.CATEGORY_UI)
|
||||
network_manager.set_local_player_count(local_count)
|
||||
is_auto_joining = true
|
||||
# Create timer for retrying room fetches
|
||||
room_fetch_timer = Timer.new()
|
||||
room_fetch_timer.name = "RoomFetchTimer"
|
||||
room_fetch_timer.wait_time = 2.0 # Retry every 2 seconds
|
||||
room_fetch_timer.timeout.connect(_retry_room_fetch)
|
||||
room_fetch_timer.autostart = false
|
||||
add_child(room_fetch_timer)
|
||||
# Connect to rooms_fetched signal (not one-shot, so we can keep retrying)
|
||||
if not network_manager.rooms_fetched.is_connected(_on_rooms_fetched_auto_join):
|
||||
network_manager.rooms_fetched.connect(_on_rooms_fetched_auto_join)
|
||||
# Show room fetch status UI and start fetching
|
||||
_show_room_fetch_status()
|
||||
_start_room_fetch()
|
||||
elif not join_address.is_empty():
|
||||
LogManager.log("Auto-joining to " + join_address + " due to --join argument", LogManager.CATEGORY_UI)
|
||||
address_input.text = join_address
|
||||
network_manager.set_local_player_count(local_count)
|
||||
if network_manager.join_game(join_address):
|
||||
# Connection callback will handle starting the game
|
||||
pass
|
||||
else:
|
||||
# ENet mode without address - need a default address
|
||||
var default_address = "127.0.0.1"
|
||||
LogManager.log("Auto-joining to " + default_address + " due to --join argument (ENet mode, no address provided)", LogManager.CATEGORY_UI)
|
||||
address_input.text = default_address
|
||||
network_manager.set_local_player_count(local_count)
|
||||
if network_manager.join_game(default_address):
|
||||
# Connection callback will handle starting the game
|
||||
pass
|
||||
|
||||
func _on_rooms_fetched_display(rooms: Array):
|
||||
"""Display available rooms when fetched (non-auto-join mode)"""
|
||||
# Only handle if not in auto-join mode (auto-join has its own handler)
|
||||
if is_auto_joining:
|
||||
return # Let auto-join handler take care of it
|
||||
|
||||
# Hide loading indicator - request completed
|
||||
_hide_loading_indicator()
|
||||
|
||||
# Update last fetch time
|
||||
_update_last_fetch_time()
|
||||
|
||||
# Clear existing room list
|
||||
_clear_room_list()
|
||||
|
||||
# Display rooms if any are found
|
||||
if rooms.is_empty():
|
||||
LogManager.log("GameUI: No available rooms found", LogManager.CATEGORY_UI)
|
||||
# Keep room fetch status visible so user can see "Last fetched: ..." even with no rooms
|
||||
# Add a "No rooms available" message
|
||||
_add_room_list_header("No available rooms")
|
||||
else:
|
||||
LogManager.log("GameUI: Found " + str(rooms.size()) + " available room(s)", LogManager.CATEGORY_UI)
|
||||
# Add header
|
||||
_add_room_list_header("Available Rooms:")
|
||||
# Add each room to the list
|
||||
for room in rooms:
|
||||
var room_code = room.get("room", "")
|
||||
var players = room.get("players", 0)
|
||||
var level = room.get("level", 1)
|
||||
_add_room_item(room_code, players, level)
|
||||
|
||||
func _on_rooms_fetched_auto_join(rooms: Array):
|
||||
"""Auto-join the first available room when --join --webrtc is used without address"""
|
||||
if not is_auto_joining:
|
||||
return # Not in auto-join mode, ignore
|
||||
|
||||
# Hide loading indicator - request completed
|
||||
_hide_loading_indicator()
|
||||
|
||||
# Update last fetch time
|
||||
_update_last_fetch_time()
|
||||
|
||||
if rooms.is_empty():
|
||||
LogManager.log("No available rooms found, will retry in 2 seconds...", LogManager.CATEGORY_UI)
|
||||
# Start timer to retry fetching
|
||||
if room_fetch_timer:
|
||||
room_fetch_timer.start()
|
||||
return
|
||||
|
||||
# Stop retrying - we found rooms!
|
||||
if room_fetch_timer:
|
||||
room_fetch_timer.stop()
|
||||
is_auto_joining = false
|
||||
|
||||
# Hide room fetch status UI
|
||||
_hide_room_fetch_status()
|
||||
|
||||
# Disconnect from signal since we're done
|
||||
if network_manager.rooms_fetched.is_connected(_on_rooms_fetched_auto_join):
|
||||
network_manager.rooms_fetched.disconnect(_on_rooms_fetched_auto_join)
|
||||
|
||||
# Sort rooms by player count (prefer rooms with more players, but not full)
|
||||
rooms.sort_custom(func(a, b): return a.get("players", 0) < b.get("players", 0))
|
||||
|
||||
# Try to join the first room
|
||||
var room = rooms[0]
|
||||
var room_code = room.get("room", "")
|
||||
if room_code.is_empty():
|
||||
LogManager.log("Room code is empty, cannot join - will retry", LogManager.CATEGORY_UI)
|
||||
# Restart timer to retry
|
||||
if room_fetch_timer:
|
||||
room_fetch_timer.start()
|
||||
is_auto_joining = true
|
||||
# Keep showing status UI
|
||||
return
|
||||
|
||||
# Get local player count from spinbox (same as _on_join_pressed does)
|
||||
var local_count = int(local_players_spinbox.value)
|
||||
|
||||
LogManager.log("Auto-joining room: " + room_code + " (players: " + str(room.get("players", 0)) + ", level: " + str(room.get("level", 1)) + ")", LogManager.CATEGORY_UI)
|
||||
address_input.text = room_code
|
||||
network_manager.set_local_player_count(local_count)
|
||||
if network_manager.join_game(room_code):
|
||||
# Connection callback will handle starting the game
|
||||
pass
|
||||
|
||||
func _retry_room_fetch():
|
||||
"""Retry fetching available rooms"""
|
||||
if not is_auto_joining:
|
||||
if room_fetch_timer:
|
||||
room_fetch_timer.stop()
|
||||
return
|
||||
|
||||
LogManager.log("Retrying room fetch...", LogManager.CATEGORY_UI)
|
||||
_start_room_fetch()
|
||||
|
||||
func _start_room_fetch():
|
||||
"""Start fetching rooms and show loading indicator"""
|
||||
# Only fetch if WebRTC mode and not hosting
|
||||
if network_manager.network_mode != 1: # Not WebRTC
|
||||
return
|
||||
|
||||
if is_hosting or network_manager.is_hosting:
|
||||
LogManager.log("GameUI: Skipping room fetch - we are hosting", LogManager.CATEGORY_UI)
|
||||
return
|
||||
|
||||
# Show the status container and loading indicator
|
||||
_show_room_fetch_status()
|
||||
_show_loading_indicator()
|
||||
|
||||
# Check if a request is already in progress before starting a new one
|
||||
var fetch_result = network_manager.fetch_available_rooms()
|
||||
if not fetch_result:
|
||||
# Request failed or is already in progress - hide loading indicator
|
||||
_hide_loading_indicator()
|
||||
# Start timer to retry (only in auto-join mode)
|
||||
if is_auto_joining and room_fetch_timer:
|
||||
room_fetch_timer.start()
|
||||
|
||||
func _show_room_fetch_status():
|
||||
"""Show the room fetch status UI"""
|
||||
if room_fetch_status_container:
|
||||
room_fetch_status_container.visible = true
|
||||
if last_fetch_label:
|
||||
last_fetch_label.text = "Last fetched: Never"
|
||||
# Create room list container if it doesn't exist
|
||||
_create_room_list_container()
|
||||
# Create refresh button if it doesn't exist
|
||||
_create_refresh_button()
|
||||
|
||||
func _show_loading_indicator():
|
||||
"""Show the loading indicator"""
|
||||
if loading_label:
|
||||
loading_label.text = "Loading rooms..."
|
||||
if loading_spinner:
|
||||
loading_spinner.text = "⏳"
|
||||
|
||||
func _hide_loading_indicator():
|
||||
"""Hide the loading indicator"""
|
||||
if loading_label:
|
||||
loading_label.text = ""
|
||||
if loading_spinner:
|
||||
loading_spinner.text = ""
|
||||
|
||||
func _hide_room_fetch_status():
|
||||
"""Hide the room fetch status UI"""
|
||||
if room_fetch_status_container:
|
||||
room_fetch_status_container.visible = false
|
||||
|
||||
func _create_refresh_button():
|
||||
"""Create a refresh button for manually reloading the room list"""
|
||||
if refresh_button:
|
||||
return # Already exists
|
||||
|
||||
if not room_fetch_status_container:
|
||||
return
|
||||
|
||||
# Create HBoxContainer for refresh button (below last fetch label)
|
||||
var refresh_container = HBoxContainer.new()
|
||||
refresh_container.name = "RefreshButtonContainer"
|
||||
refresh_container.alignment = BoxContainer.ALIGNMENT_END
|
||||
|
||||
# Create refresh button
|
||||
refresh_button = Button.new()
|
||||
refresh_button.name = "RefreshButton"
|
||||
refresh_button.text = "Refresh Rooms"
|
||||
refresh_button.pressed.connect(_on_refresh_button_pressed)
|
||||
|
||||
refresh_container.add_child(refresh_button)
|
||||
|
||||
# Add refresh container after LastFetchLabel (or at the end if LastFetchLabel doesn't exist)
|
||||
var last_fetch_node = room_fetch_status_container.get_node_or_null("LastFetchLabel")
|
||||
if last_fetch_node:
|
||||
var index = last_fetch_node.get_index() + 1
|
||||
room_fetch_status_container.add_child(refresh_container)
|
||||
room_fetch_status_container.move_child(refresh_container, index)
|
||||
else:
|
||||
room_fetch_status_container.add_child(refresh_container)
|
||||
|
||||
# Create cooldown timer
|
||||
refresh_cooldown_timer = Timer.new()
|
||||
refresh_cooldown_timer.name = "RefreshCooldownTimer"
|
||||
refresh_cooldown_timer.wait_time = 5.0
|
||||
refresh_cooldown_timer.one_shot = true
|
||||
refresh_cooldown_timer.timeout.connect(_on_refresh_cooldown_finished)
|
||||
refresh_cooldown_timer.autostart = false
|
||||
add_child(refresh_cooldown_timer)
|
||||
|
||||
func _on_refresh_button_pressed():
|
||||
"""Handle refresh button click"""
|
||||
# Disable button and start cooldown
|
||||
if refresh_button:
|
||||
refresh_button.disabled = true
|
||||
refresh_button.text = "Refreshing..."
|
||||
|
||||
if refresh_cooldown_timer:
|
||||
refresh_cooldown_timer.start()
|
||||
|
||||
# Fetch rooms
|
||||
_start_room_fetch()
|
||||
|
||||
func _on_refresh_cooldown_finished():
|
||||
"""Re-enable refresh button after cooldown"""
|
||||
if refresh_button:
|
||||
refresh_button.disabled = false
|
||||
refresh_button.text = "Refresh Rooms"
|
||||
|
||||
func _update_last_fetch_time():
|
||||
"""Update the last fetch time label with current datetime"""
|
||||
if last_fetch_label:
|
||||
var now = Time.get_datetime_dict_from_system()
|
||||
var time_str = "%02d:%02d:%02d" % [now.hour, now.minute, now.second]
|
||||
last_fetch_label.text = "Last fetched: " + time_str
|
||||
|
||||
func _create_room_list_container():
|
||||
"""Create the container for displaying available rooms"""
|
||||
if room_list_container:
|
||||
return # Already exists
|
||||
|
||||
if not room_fetch_status_container:
|
||||
return
|
||||
|
||||
# Create a ScrollContainer for the room list
|
||||
var scroll_container = ScrollContainer.new()
|
||||
scroll_container.name = "RoomListScrollContainer"
|
||||
scroll_container.custom_minimum_size = Vector2(0, 150) # Set a reasonable height
|
||||
scroll_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
# Create VBoxContainer inside scroll container
|
||||
room_list_container = VBoxContainer.new()
|
||||
room_list_container.name = "RoomListContainer"
|
||||
room_list_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
scroll_container.add_child(room_list_container)
|
||||
|
||||
# Add scroll container to room_fetch_status_container (after LastFetchLabel)
|
||||
room_fetch_status_container.add_child(scroll_container)
|
||||
|
||||
func _clear_room_list():
|
||||
"""Clear all room items from the list"""
|
||||
if room_list_container:
|
||||
for child in room_list_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
func _add_room_list_header(text: String):
|
||||
"""Add a header label to the room list"""
|
||||
if not room_list_container:
|
||||
_create_room_list_container()
|
||||
if not room_list_container:
|
||||
return
|
||||
|
||||
var label = Label.new()
|
||||
label.text = text
|
||||
label.add_theme_font_size_override("font_size", 14)
|
||||
room_list_container.add_child(label)
|
||||
|
||||
func _add_room_item(room_code: String, players: int, level: int):
|
||||
"""Add a room item with join button to the list"""
|
||||
if not room_list_container:
|
||||
_create_room_list_container()
|
||||
if not room_list_container:
|
||||
return
|
||||
|
||||
# Create HBoxContainer for this room
|
||||
var room_row = HBoxContainer.new()
|
||||
room_row.name = "RoomRow_" + room_code
|
||||
|
||||
# Room code label
|
||||
var code_label = Label.new()
|
||||
code_label.text = room_code
|
||||
code_label.custom_minimum_size = Vector2(100, 0)
|
||||
code_label.add_theme_font_size_override("font_size", 12)
|
||||
room_row.add_child(code_label)
|
||||
|
||||
# Info label (players, level)
|
||||
var info_label = Label.new()
|
||||
info_label.text = "Players: %d | Level: %d" % [players, level]
|
||||
info_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
info_label.add_theme_font_size_override("font_size", 12)
|
||||
room_row.add_child(info_label)
|
||||
|
||||
# Join button
|
||||
var room_join_button = Button.new()
|
||||
room_join_button.text = "Join"
|
||||
room_join_button.custom_minimum_size = Vector2(80, 0)
|
||||
# Connect button to join function
|
||||
room_join_button.pressed.connect(func(): _join_room(room_code))
|
||||
room_row.add_child(room_join_button)
|
||||
|
||||
room_list_container.add_child(room_row)
|
||||
|
||||
func _join_room(room_code: String):
|
||||
"""Join a room by setting the address and clicking join"""
|
||||
if room_code.is_empty():
|
||||
return
|
||||
|
||||
# Set the address input
|
||||
if address_input:
|
||||
address_input.text = room_code
|
||||
|
||||
# Get local player count
|
||||
var local_count = int(local_players_spinbox.value)
|
||||
network_manager.set_local_player_count(local_count)
|
||||
|
||||
# Join the game
|
||||
if network_manager.join_game(room_code):
|
||||
LogManager.log("Joining room: " + room_code, LogManager.CATEGORY_UI)
|
||||
|
||||
func _on_network_mode_changed(index: int):
|
||||
# On web builds, index 0 = WebRTC, index 1 = WebSocket
|
||||
@@ -153,9 +517,17 @@ func _on_network_mode_changed(index: int):
|
||||
address_input.placeholder_text = "Enter Room Code (e.g., ABC123)"
|
||||
|
||||
var mode_names = ["ENet", "WebRTC", "WebSocket"]
|
||||
print("GameUI: Network mode changed to: ", mode_names[actual_mode])
|
||||
LogManager.log("GameUI: Network mode changed to: " + mode_names[actual_mode], LogManager.CATEGORY_UI)
|
||||
|
||||
# If WebRTC is selected, fetch available rooms (unless we're auto-joining or hosting)
|
||||
if actual_mode == 1 and not is_auto_joining and not is_hosting: # WebRTC mode
|
||||
_start_room_fetch()
|
||||
elif actual_mode != 1: # Not WebRTC mode
|
||||
# Hide room fetch status if switching away from WebRTC
|
||||
_hide_room_fetch_status()
|
||||
|
||||
func _on_host_pressed():
|
||||
is_hosting = true # Set flag so we don't fetch rooms
|
||||
var local_count = int(local_players_spinbox.value)
|
||||
network_manager.set_local_player_count(local_count)
|
||||
|
||||
@@ -171,42 +543,115 @@ func _on_host_pressed():
|
||||
_start_game()
|
||||
|
||||
func _on_join_pressed():
|
||||
# Reset error state when attempting new connection
|
||||
connection_error_shown = false
|
||||
_hide_connection_error()
|
||||
is_joining_attempt = true
|
||||
var address = address_input.text
|
||||
if address.is_empty():
|
||||
var mode = network_manager.network_mode
|
||||
if mode == 1 or mode == 2: # WebRTC or WebSocket
|
||||
print("Error: Please enter a room code")
|
||||
LogManager.log("Error: Please enter a room code", LogManager.CATEGORY_UI)
|
||||
return
|
||||
else: # ENet
|
||||
else: # ENet mode without address - use default
|
||||
address = "127.0.0.1"
|
||||
|
||||
var local_count = int(local_players_spinbox.value)
|
||||
network_manager.set_local_player_count(local_count)
|
||||
|
||||
if network_manager.join_game(address):
|
||||
last_join_address = address
|
||||
var mode = network_manager.network_mode
|
||||
if mode == 1: # WebRTC
|
||||
print("Joining WebRTC game with room code: ", address)
|
||||
LogManager.log("Joining WebRTC game with room code: " + address, LogManager.CATEGORY_UI)
|
||||
elif mode == 2: # WebSocket
|
||||
print("Joining WebSocket game with room code: ", address)
|
||||
LogManager.log("Joining WebSocket game with room code: " + address, LogManager.CATEGORY_UI)
|
||||
else: # ENet
|
||||
print("Joining ENet game at ", address, " with ", local_count, " local players")
|
||||
LogManager.log("Joining ENet game at " + address + " with " + str(local_count) + " local players", LogManager.CATEGORY_UI)
|
||||
|
||||
func _on_connection_succeeded():
|
||||
print("Connection succeeded, starting game")
|
||||
_start_game()
|
||||
LogManager.log("GameUI: Connection succeeded signal received, starting game", LogManager.CATEGORY_UI)
|
||||
is_joining_attempt = false
|
||||
# Check if node is still valid before starting game
|
||||
if not is_inside_tree():
|
||||
LogManager.log_error("GameUI: Cannot start game - node not in tree", LogManager.CATEGORY_UI)
|
||||
return
|
||||
# Use call_deferred to ensure we're in a safe state to change scenes
|
||||
call_deferred("_start_game")
|
||||
|
||||
func _on_connection_failed():
|
||||
print("Connection failed")
|
||||
# Show error message
|
||||
var error_label = Label.new()
|
||||
error_label.text = "Failed to connect to server"
|
||||
error_label.modulate = Color.RED
|
||||
main_menu.add_child(error_label)
|
||||
LogManager.log("Connection failed", LogManager.CATEGORY_UI)
|
||||
if connection_error_shown:
|
||||
# Already shown, don't spam
|
||||
return
|
||||
|
||||
connection_error_shown = true
|
||||
var mode = network_manager.network_mode
|
||||
var mode_name = "ENet"
|
||||
if mode == 1:
|
||||
mode_name = "WebRTC"
|
||||
elif mode == 2:
|
||||
mode_name = "WebSocket"
|
||||
|
||||
if is_joining_attempt:
|
||||
var code_hint = (" (" + last_join_address + ")") if not last_join_address.is_empty() else ""
|
||||
_show_connection_error("Failed to join room" + code_hint + ". Did you enter the correct code?")
|
||||
# For WebRTC, refresh signaling connection after a failed join
|
||||
if mode == 1 and network_manager and network_manager.has_method("refresh_webrtc_servers"):
|
||||
network_manager.refresh_webrtc_servers(last_join_address)
|
||||
# Show room list UI and refresh rooms after a failed WebRTC join
|
||||
_show_room_fetch_status()
|
||||
_show_loading_indicator()
|
||||
_start_room_fetch()
|
||||
else:
|
||||
_show_connection_error("Connection failed (" + mode_name + "). Please try again.")
|
||||
|
||||
func _show_connection_error(message: String):
|
||||
"""Show a temporary error message in the UI"""
|
||||
if not main_menu:
|
||||
return
|
||||
|
||||
# Remove existing error label if any
|
||||
_hide_connection_error()
|
||||
|
||||
# Create error label
|
||||
connection_error_label = Label.new()
|
||||
connection_error_label.name = "ConnectionErrorLabel"
|
||||
connection_error_label.text = message
|
||||
connection_error_label.modulate = Color.RED
|
||||
connection_error_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
connection_error_label.add_theme_font_size_override("font_size", 14)
|
||||
|
||||
# Add to main menu VBoxContainer (after the title)
|
||||
var vbox = main_menu.get_node_or_null("VBoxContainer")
|
||||
if vbox:
|
||||
# Insert after title (index 0) or at the beginning
|
||||
vbox.add_child(connection_error_label)
|
||||
vbox.move_child(connection_error_label, 1) # Move to position 1 (after title)
|
||||
|
||||
# Auto-hide after 5 seconds
|
||||
await get_tree().create_timer(5.0).timeout
|
||||
_hide_connection_error()
|
||||
|
||||
func _hide_connection_error():
|
||||
"""Hide and remove the connection error label"""
|
||||
if connection_error_label:
|
||||
connection_error_label.queue_free()
|
||||
connection_error_label = null
|
||||
|
||||
func _start_game():
|
||||
# Check if node is still in the tree before trying to access get_tree()
|
||||
if not is_inside_tree():
|
||||
LogManager.log_error("GameUI: Cannot change scene - node is not in tree", LogManager.CATEGORY_UI)
|
||||
return
|
||||
|
||||
# Hide menu
|
||||
main_menu.visible = false
|
||||
if main_menu:
|
||||
main_menu.visible = false
|
||||
|
||||
# Load the game scene
|
||||
get_tree().change_scene_to_file("res://scenes/game_world.tscn")
|
||||
var tree = get_tree()
|
||||
if tree:
|
||||
tree.change_scene_to_file("res://scenes/game_world.tscn")
|
||||
else:
|
||||
LogManager.log_error("GameUI: Cannot change scene - get_tree() is null", LogManager.CATEGORY_UI)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ var texture_progress_bar_boss_hp: TextureProgressBar = null
|
||||
var label_host: Label = null
|
||||
var label_player_count: Label = null
|
||||
var label_room_code: Label = null
|
||||
var label_disconnected: Label = null
|
||||
|
||||
var game_world: Node = null
|
||||
var network_manager: Node = null
|
||||
@@ -41,6 +42,7 @@ func _ready():
|
||||
label_host = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelHost")
|
||||
label_player_count = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelPlayerCount")
|
||||
label_room_code = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelRoomCode")
|
||||
label_disconnected = get_node_or_null("CenterTop/LabelDisconnected")
|
||||
|
||||
# Find network manager
|
||||
network_manager = get_node_or_null("/root/NetworkManager")
|
||||
@@ -48,6 +50,8 @@ func _ready():
|
||||
# Connect to player connection signals to update player count
|
||||
network_manager.player_connected.connect(_on_player_connected)
|
||||
network_manager.player_disconnected.connect(_on_player_disconnected)
|
||||
network_manager.connection_failed.connect(_on_connection_failed)
|
||||
network_manager.connection_succeeded.connect(_on_connection_succeeded)
|
||||
|
||||
# Debug: Check if nodes were found
|
||||
if not label_time_value:
|
||||
@@ -86,6 +90,21 @@ func _on_player_connected(_peer_id: int, _player_info: Dictionary):
|
||||
func _on_player_disconnected(_peer_id: int, _player_info: Dictionary):
|
||||
_update_host_info()
|
||||
|
||||
func _on_connection_failed():
|
||||
# Show disconnection message
|
||||
if label_disconnected:
|
||||
label_disconnected.visible = true
|
||||
# Show different message for host vs joiner
|
||||
if network_manager and network_manager.is_hosting:
|
||||
label_disconnected.text = "Lost connection to Matchbox server - Retrying..."
|
||||
else:
|
||||
label_disconnected.text = "Disconnected - Reconnecting..."
|
||||
|
||||
func _on_connection_succeeded():
|
||||
# Hide disconnection message
|
||||
if label_disconnected:
|
||||
label_disconnected.visible = false
|
||||
|
||||
func _update_host_info():
|
||||
if not network_manager:
|
||||
return
|
||||
|
||||
@@ -17,6 +17,7 @@ var is_being_held: bool = false
|
||||
var held_by_player = null
|
||||
var is_frozen: bool = false
|
||||
var thrown_by_player = null # Track who threw this box
|
||||
var is_broken: bool = false
|
||||
|
||||
# Physics for thrown objects
|
||||
var throw_velocity: Vector2 = Vector2.ZERO
|
||||
@@ -47,6 +48,17 @@ func _ready():
|
||||
collision_layer = 2 # Layer 2 for objects
|
||||
collision_mask = 1 | 2 | 4 # Collide with players, other objects, and walls
|
||||
|
||||
# Ensure deterministic name for network sync
|
||||
if has_meta("object_index") and not name.begins_with("InteractableObject_"):
|
||||
name = "InteractableObject_%d" % get_meta("object_index")
|
||||
elif name.begins_with("InteractableObject_"):
|
||||
# Ensure meta matches name if it already has a consistent name
|
||||
var index_str = name.substr(20)
|
||||
if index_str.is_valid_int():
|
||||
var name_index = index_str.to_int()
|
||||
if not has_meta("object_index") or get_meta("object_index") != name_index:
|
||||
set_meta("object_index", name_index)
|
||||
|
||||
# No gravity in top-down
|
||||
motion_mode = MOTION_MODE_FLOATING
|
||||
|
||||
@@ -164,9 +176,14 @@ func _handle_air_collision():
|
||||
|
||||
# Box breaks (only if destroyable)
|
||||
if is_destroyable:
|
||||
# Sync break to OTHER clients via RPC BEFORE breaking locally
|
||||
# Use game_world to route the RPC to avoid node path resolution issues
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_object_break", [name])
|
||||
|
||||
_break_into_pieces()
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
_sync_break.rpc()
|
||||
|
||||
return
|
||||
|
||||
@@ -181,11 +198,14 @@ func _handle_air_collision():
|
||||
|
||||
# Hit a player! Break locally and sync to others (only if destroyable)
|
||||
if is_destroyable:
|
||||
_break_into_pieces()
|
||||
# Sync break to OTHER clients via RPC BEFORE breaking locally
|
||||
# Use game_world to route the RPC to avoid node path resolution issues
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_object_break", [name])
|
||||
|
||||
# Sync break to OTHER clients
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
_sync_break.rpc()
|
||||
_break_into_pieces()
|
||||
|
||||
# Damage and knockback player using RPC
|
||||
# Pass the thrower's position for accurate direction
|
||||
@@ -214,24 +234,28 @@ func _handle_air_collision():
|
||||
|
||||
# Hit another box! Break both locally (only if destroyable)
|
||||
if is_destroyable:
|
||||
# Sync break to OTHER clients via RPC BEFORE breaking locally
|
||||
# Use game_world to route the RPC to avoid node path resolution issues
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_object_break", [name])
|
||||
# Tell the other box to break too
|
||||
if collider.has_method("can_be_destroyed") and collider.can_be_destroyed():
|
||||
game_world._rpc_to_ready_peers("_sync_object_break", [collider.name])
|
||||
|
||||
_break_into_pieces()
|
||||
if collider.has_method("_break_into_pieces") and collider.has_method("can_be_destroyed") and collider.can_be_destroyed():
|
||||
collider._break_into_pieces()
|
||||
|
||||
# Sync break to OTHER clients
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
_sync_break.rpc()
|
||||
# Tell the other box to break too
|
||||
if collider.has_method("_sync_break") and collider.has_method("can_be_destroyed") and collider.can_be_destroyed():
|
||||
collider._sync_break.rpc()
|
||||
|
||||
print(name, " hit another box!")
|
||||
return
|
||||
|
||||
func _break_into_pieces():
|
||||
func _break_into_pieces(silent: bool = false):
|
||||
# Only break if destroyable
|
||||
if not is_destroyable:
|
||||
if not is_destroyable or is_broken:
|
||||
return
|
||||
is_broken = true
|
||||
|
||||
var sprite_texture = $Sprite2D.texture
|
||||
var frame_width = sprite_texture.get_width() / $Sprite2D.hframes
|
||||
@@ -254,27 +278,28 @@ func _break_into_pieces():
|
||||
Rect2(frame_x + frame_width / 2, frame_y + frame_height / 2, frame_width / 2, frame_height / 2) # Bottom-right
|
||||
]
|
||||
|
||||
for i in range(4):
|
||||
var tp = tileParticleScene.instantiate() as CharacterBody2D
|
||||
var spr2D = tp.get_node("Sprite2D") as Sprite2D
|
||||
tp.global_position = global_position
|
||||
if not silent:
|
||||
for i in range(4):
|
||||
var tp = tileParticleScene.instantiate() as CharacterBody2D
|
||||
var spr2D = tp.get_node("Sprite2D") as Sprite2D
|
||||
tp.global_position = global_position
|
||||
|
||||
# Set up the sprite's texture and region
|
||||
spr2D.texture = sprite_texture
|
||||
spr2D.region_enabled = true
|
||||
spr2D.region_rect = regions[i]
|
||||
|
||||
# Add some randomness to the velocity
|
||||
var speed = randf_range(170, 200)
|
||||
var dir = directions[i] + Vector2(randf_range(-0.2, 0.2), randf_range(-0.2, 0.2))
|
||||
tp.velocity = dir * speed
|
||||
|
||||
# Add some rotation
|
||||
tp.angular_velocity = randf_range(-7, 7)
|
||||
|
||||
get_parent().call_deferred("add_child", tp)
|
||||
|
||||
# Set up the sprite's texture and region
|
||||
spr2D.texture = sprite_texture
|
||||
spr2D.region_enabled = true
|
||||
spr2D.region_rect = regions[i]
|
||||
|
||||
# Add some randomness to the velocity
|
||||
var speed = randf_range(170, 200)
|
||||
var dir = directions[i] + Vector2(randf_range(-0.2, 0.2), randf_range(-0.2, 0.2))
|
||||
tp.velocity = dir * speed
|
||||
|
||||
# Add some rotation
|
||||
tp.angular_velocity = randf_range(-7, 7)
|
||||
|
||||
get_parent().call_deferred("add_child", tp)
|
||||
|
||||
play_destroy_sound()
|
||||
play_destroy_sound()
|
||||
self.set_deferred("collision_layer", 0)
|
||||
$Shadow.visible = false
|
||||
$Sprite2DAbove.visible = false
|
||||
@@ -292,18 +317,47 @@ func _break_into_pieces():
|
||||
ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world)
|
||||
print(name, " dropped item: ", item.item_name, " when broken")
|
||||
|
||||
if ($SfxShatter.playing):
|
||||
await $SfxShatter.finished
|
||||
if ($SfxBreakCrate.playing):
|
||||
await $SfxBreakCrate.finished
|
||||
if not silent:
|
||||
if ($SfxShatter.playing):
|
||||
await $SfxShatter.finished
|
||||
if ($SfxBreakCrate.playing):
|
||||
await $SfxBreakCrate.finished
|
||||
# Remove self
|
||||
queue_free()
|
||||
|
||||
func can_be_grabbed() -> bool:
|
||||
return is_grabbable and not is_being_held
|
||||
|
||||
func _get_configured_object_type() -> String:
|
||||
# Prefer the configured type from dungeon data if available
|
||||
var idx = -1
|
||||
if name.begins_with("InteractableObject_"):
|
||||
var index_str = name.substr(20)
|
||||
if index_str.is_valid_int():
|
||||
idx = index_str.to_int()
|
||||
elif has_meta("object_index"):
|
||||
idx = get_meta("object_index")
|
||||
|
||||
if idx >= 0:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and "dungeon_data" in game_world and game_world.dungeon_data.has("interactable_objects"):
|
||||
var objects = game_world.dungeon_data.interactable_objects
|
||||
if idx < objects.size():
|
||||
var obj_data = objects[idx]
|
||||
if obj_data is Dictionary and obj_data.has("type"):
|
||||
return obj_data.type
|
||||
|
||||
return object_type
|
||||
|
||||
func can_be_lifted() -> bool:
|
||||
# Can be lifted if it's liftable (being held is OK - we're checking if it CAN be lifted)
|
||||
var resolved_type = object_type
|
||||
if resolved_type == "":
|
||||
resolved_type = _get_configured_object_type()
|
||||
if resolved_type in ["Box", "Pot", "LiftableBarrel"]:
|
||||
return true
|
||||
if resolved_type in ["Chest", "Pillar", "PushableBarrel", "PushableHighBox"]:
|
||||
return false
|
||||
return is_liftable
|
||||
|
||||
func can_be_thrown() -> bool:
|
||||
@@ -321,8 +375,16 @@ func on_grabbed(by_player):
|
||||
# Client - send request to server
|
||||
if by_player:
|
||||
var player_peer_id = by_player.get_multiplayer_authority()
|
||||
print("Chest: Client sending RPC to open chest, player_peer_id: ", player_peer_id)
|
||||
_request_chest_open.rpc_id(1, player_peer_id)
|
||||
# Use consistent object name based on object_index to avoid NodePath issues
|
||||
var chest_name = name
|
||||
if has_meta("object_index"):
|
||||
chest_name = "InteractableObject_%d" % get_meta("object_index")
|
||||
print("Chest: Client sending RPC to open chest, player_peer_id: ", player_peer_id, " chest_name: ", chest_name)
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_request_chest_open_by_name"):
|
||||
game_world._request_chest_open_by_name.rpc_id(1, chest_name, player_peer_id)
|
||||
else:
|
||||
push_warning("Chest: GameWorld not ready, cannot send chest open request for " + chest_name)
|
||||
else:
|
||||
# Server or single player - open directly
|
||||
_open_chest(by_player)
|
||||
@@ -397,10 +459,10 @@ func _sync_box_state(pos: Vector2, vel: Vector2, z_pos: float, z_vel: float, air
|
||||
is_airborne = airborne
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_break():
|
||||
func _sync_break(silent: bool = false):
|
||||
# Sync break to all clients including server (called by whoever breaks the box)
|
||||
if not is_queued_for_deletion():
|
||||
_break_into_pieces()
|
||||
if not is_queued_for_deletion() and not is_broken:
|
||||
_break_into_pieces(silent)
|
||||
|
||||
# Object type setup functions
|
||||
func setup_pot():
|
||||
@@ -448,7 +510,21 @@ func setup_box():
|
||||
|
||||
var box_frames = [7, 26]
|
||||
if sprite:
|
||||
sprite.frame = box_frames[randi() % box_frames.size()]
|
||||
# Use deterministic randomness based on dungeon seed and position
|
||||
# This ensures host and clients get the same box variant
|
||||
var box_seed = 0
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and "dungeon_seed" in game_world:
|
||||
box_seed = game_world.dungeon_seed
|
||||
# Add position and object_index to seed to make each box unique but deterministic
|
||||
box_seed += int(global_position.x) * 1000 + int(global_position.y)
|
||||
if has_meta("object_index"):
|
||||
box_seed += get_meta("object_index") * 10000
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = box_seed
|
||||
var index = rng.randi() % box_frames.size()
|
||||
sprite.frame = box_frames[index]
|
||||
|
||||
func setup_chest():
|
||||
object_type = "Chest"
|
||||
@@ -503,7 +579,19 @@ func setup_pushable_high_box():
|
||||
|
||||
var bottom_frames = [24, 25]
|
||||
var top_frames = [5, 6]
|
||||
var index = randi() % bottom_frames.size()
|
||||
|
||||
# Use deterministic randomness based on dungeon seed and position
|
||||
# This ensures host and clients get the same chest variant
|
||||
var highbox_seed = 0
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and "dungeon_seed" in game_world:
|
||||
highbox_seed = game_world.dungeon_seed
|
||||
# Add position to seed to make each chest unique but deterministic
|
||||
highbox_seed += int(global_position.x) * 1000 + int(global_position.y)
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = highbox_seed
|
||||
var index = rng.randi() % bottom_frames.size()
|
||||
|
||||
if sprite:
|
||||
sprite.frame = bottom_frames[index]
|
||||
@@ -519,6 +607,14 @@ func _open_chest(by_player: Node = null):
|
||||
return
|
||||
$SfxOpenChest.play()
|
||||
is_chest_opened = true
|
||||
|
||||
# Track opened chest for syncing to new clients
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and has_meta("object_index"):
|
||||
var obj_index = get_meta("object_index")
|
||||
game_world.opened_chests[obj_index] = true
|
||||
LogManager.log("Chest: Tracked opened chest with index " + str(obj_index), LogManager.CATEGORY_NETWORK)
|
||||
if sprite and chest_opened_frame >= 0:
|
||||
sprite.frame = chest_opened_frame
|
||||
|
||||
@@ -579,7 +675,12 @@ func _open_chest(by_player: Node = null):
|
||||
# Sync chest opening visual to all clients (item already given on server)
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
var player_peer_id = by_player.get_multiplayer_authority() if by_player else 0
|
||||
_sync_chest_open.rpc(selected_loot.type if by_player else "coin", player_peer_id)
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_rpc_to_ready_peers"):
|
||||
var chest_name = name
|
||||
if has_meta("object_index"):
|
||||
chest_name = "InteractableObject_%d" % get_meta("object_index")
|
||||
game_world._rpc_to_ready_peers("_sync_chest_open_by_name", [chest_name, selected_loot.type if by_player else "coin", player_peer_id])
|
||||
else:
|
||||
push_error("Chest: ERROR - No valid player to give item to!")
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ static func spawn_item_loot(item: Item, position: Vector2, entities_node: Node,
|
||||
loot.set_meta("loot_id", loot_id)
|
||||
|
||||
# Sync item data to clients
|
||||
game_world._sync_item_loot_spawn.rpc(safe_spawn_pos, item.save(), initial_velocity, random_velocity_z, loot_id)
|
||||
game_world._rpc_to_ready_peers("_sync_item_loot_spawn", [safe_spawn_pos, item.save(), initial_velocity, random_velocity_z, loot_id])
|
||||
|
||||
print("ItemLootHelper: Spawned item loot: ", item.item_name, " at ", safe_spawn_pos)
|
||||
return loot
|
||||
|
||||
80
src/scripts/log_manager.gd
Normal file
80
src/scripts/log_manager.gd
Normal file
@@ -0,0 +1,80 @@
|
||||
extends Node
|
||||
|
||||
# Log Manager - Centralized logging system with category-based filtering
|
||||
# Categories can be enabled/disabled independently for debugging
|
||||
|
||||
# Log categories
|
||||
const CATEGORY_NETWORK = "NETWORK"
|
||||
const CATEGORY_GAMEPLAY = "GAMEPLAY"
|
||||
const CATEGORY_UI = "UI"
|
||||
const CATEGORY_DUNGEON = "DUNGEON"
|
||||
const CATEGORY_ENEMY = "ENEMY"
|
||||
const CATEGORY_PLAYER = "PLAYER"
|
||||
const CATEGORY_DOOR = "DOOR"
|
||||
const CATEGORY_INVENTORY = "INVENTORY"
|
||||
const CATEGORY_DEFAULT = "DEFAULT"
|
||||
|
||||
# Enable/disable flags for each category
|
||||
var enabled_categories: Dictionary = {
|
||||
CATEGORY_NETWORK: true, # Enable network logs for debugging
|
||||
CATEGORY_GAMEPLAY: false,
|
||||
CATEGORY_UI: false,
|
||||
CATEGORY_DUNGEON: false,
|
||||
CATEGORY_ENEMY: false,
|
||||
CATEGORY_PLAYER: false,
|
||||
CATEGORY_DOOR: false,
|
||||
CATEGORY_INVENTORY: false,
|
||||
CATEGORY_DEFAULT: false, # Disable default/unclassified logs
|
||||
}
|
||||
|
||||
# Logging helper to determine host/joiner prefix
|
||||
func _log_prefix() -> String:
|
||||
var network_manager = get_node_or_null("/root/NetworkManager")
|
||||
if network_manager and network_manager.is_hosting:
|
||||
return "[H] "
|
||||
else:
|
||||
return "[J] "
|
||||
|
||||
func log(message: String, category: String = CATEGORY_DEFAULT):
|
||||
"""Log a message if the category is enabled"""
|
||||
if enabled_categories.get(category, false):
|
||||
print(_log_prefix() + "[" + category + "] " + message)
|
||||
|
||||
func log_error(message: String, category: String = CATEGORY_DEFAULT):
|
||||
"""Log an error message if the category is enabled"""
|
||||
if enabled_categories.get(category, false):
|
||||
push_error(_log_prefix() + "[" + category + "] " + message)
|
||||
|
||||
func set_category_enabled(category: String, enabled: bool):
|
||||
"""Enable or disable a log category"""
|
||||
enabled_categories[category] = enabled
|
||||
|
||||
func is_category_enabled(category: String) -> bool:
|
||||
"""Check if a category is enabled"""
|
||||
return enabled_categories.get(category, false)
|
||||
|
||||
func enable_all_categories():
|
||||
"""Enable all log categories"""
|
||||
for category in enabled_categories:
|
||||
enabled_categories[category] = true
|
||||
|
||||
func disable_all_categories():
|
||||
"""Disable all log categories"""
|
||||
for category in enabled_categories:
|
||||
enabled_categories[category] = false
|
||||
|
||||
func enable_network_only():
|
||||
"""Enable only network-related logs (useful for debugging multiplayer)"""
|
||||
disable_all_categories()
|
||||
enabled_categories[CATEGORY_NETWORK] = true
|
||||
|
||||
# Example usage:
|
||||
# LogManager.log("This is a network message", LogManager.CATEGORY_NETWORK)
|
||||
# LogManager.log("This is a gameplay message", LogManager.CATEGORY_GAMEPLAY)
|
||||
# LogManager.set_category_enabled(LogManager.CATEGORY_GAMEPLAY, true) # Enable gameplay logs
|
||||
# LogManager.enable_network_only() # Disable all except network
|
||||
#
|
||||
# NOTE: Many scripts still use print() directly. To suppress those logs,
|
||||
# you can temporarily redirect print() output or update scripts to use LogManager.
|
||||
# For now, network-related scripts (NetworkManager, MatchboxClient, RoomRegistry)
|
||||
# use LogManager, while gameplay scripts still use print() directly.
|
||||
1
src/scripts/log_manager.gd.uid
Normal file
1
src/scripts/log_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://db2vxtkt6gnvy
|
||||
@@ -379,11 +379,10 @@ func _process_pickup_on_server(player: Node):
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_remove"):
|
||||
print("Loot: Server syncing removal of loot id=", loot_id, " at ", global_position)
|
||||
game_world._sync_loot_remove.rpc(loot_id, global_position)
|
||||
game_world._rpc_to_ready_peers("_sync_loot_remove", [loot_id, global_position])
|
||||
else:
|
||||
# Fallback: try direct RPC (may fail if node path doesn't match)
|
||||
print("Loot: Server syncing removal via direct RPC (fallback)")
|
||||
rpc("_sync_remove")
|
||||
# If GameWorld isn't ready, skip removal sync to avoid node path RPC errors
|
||||
print("Loot: GameWorld not ready, skipping removal sync for loot id=", loot_id)
|
||||
|
||||
match loot_type:
|
||||
LootType.COIN:
|
||||
@@ -395,9 +394,11 @@ func _process_pickup_on_server(player: Node):
|
||||
# Show floating text with item graphic and text
|
||||
var coin_texture = load("res://assets/gfx/pickups/gold_coin.png")
|
||||
_show_floating_text(player, "+" + str(coin_value) + " COIN", Color(1.0, 0.84, 0.0), 0.5, 0.5, coin_texture, 6, 1, 0)
|
||||
# Sync floating text to client
|
||||
# Sync floating text to client via GameWorld to avoid loot node path RPCs
|
||||
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
|
||||
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(coin_value) + " COIN", Color(1.0, 0.84, 0.0), coin_value, 0, player.get_multiplayer_authority())
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_floating_text"):
|
||||
game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(coin_value) + " COIN", Color(1.0, 0.84, 0.0), 0, player.get_multiplayer_authority())
|
||||
|
||||
self.visible = false
|
||||
|
||||
@@ -416,9 +417,11 @@ func _process_pickup_on_server(player: Node):
|
||||
# Show floating text with item graphic and heal amount
|
||||
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
|
||||
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 10)
|
||||
# Sync floating text to client
|
||||
# Sync floating text to client via GameWorld to avoid loot node path RPCs
|
||||
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
|
||||
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(int(actual_heal)) + " HP", Color.GREEN, int(actual_heal), (8 * 20) + 10, player.get_multiplayer_authority())
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_floating_text"):
|
||||
game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(int(actual_heal)) + " HP", Color.GREEN, (8 * 20) + 10, player.get_multiplayer_authority())
|
||||
|
||||
self.visible = false
|
||||
|
||||
@@ -437,9 +440,11 @@ func _process_pickup_on_server(player: Node):
|
||||
# Show floating text with item graphic and heal amount
|
||||
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
|
||||
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 11)
|
||||
# Sync floating text to client
|
||||
# Sync floating text to client via GameWorld to avoid loot node path RPCs
|
||||
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
|
||||
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(int(actual_heal)) + " HP", Color.GREEN, int(actual_heal), (8 * 20) + 11, player.get_multiplayer_authority())
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_floating_text"):
|
||||
game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(int(actual_heal)) + " HP", Color.GREEN, (8 * 20) + 11, player.get_multiplayer_authority())
|
||||
|
||||
self.visible = false
|
||||
|
||||
@@ -458,9 +463,11 @@ func _process_pickup_on_server(player: Node):
|
||||
# Show floating text with item graphic and heal amount
|
||||
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
|
||||
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 12)
|
||||
# Sync floating text to client
|
||||
# Sync floating text to client via GameWorld to avoid loot node path RPCs
|
||||
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
|
||||
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(int(actual_heal)) + " HP", Color.GREEN, int(actual_heal), (8 * 20) + 12, player.get_multiplayer_authority())
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_floating_text"):
|
||||
game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(int(actual_heal)) + " HP", Color.GREEN, (8 * 20) + 12, player.get_multiplayer_authority())
|
||||
|
||||
self.visible = false
|
||||
|
||||
@@ -477,9 +484,11 @@ func _process_pickup_on_server(player: Node):
|
||||
# Show floating text with item graphic and text
|
||||
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
|
||||
_show_floating_text(player, "+1 KEY", Color.YELLOW, 0.5, 0.5, items_texture, 20, 14, (13 * 20) + 10)
|
||||
# Sync floating text to client
|
||||
# Sync floating text to client via GameWorld to avoid loot node path RPCs
|
||||
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
|
||||
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+1 KEY", Color.YELLOW, 0, (13 * 20) + 10, player.get_multiplayer_authority())
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_floating_text"):
|
||||
game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+1 KEY", Color.YELLOW, (13 * 20) + 10, player.get_multiplayer_authority())
|
||||
|
||||
self.visible = false
|
||||
|
||||
@@ -522,9 +531,11 @@ func _process_pickup_on_server(player: Node):
|
||||
|
||||
_show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, item.spriteFrames.x, item.spriteFrames.y, item.spriteFrame)
|
||||
|
||||
# Sync floating text to client
|
||||
# Sync floating text to client via GameWorld to avoid loot node path RPCs
|
||||
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
|
||||
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, 0, item.spriteFrame, player.get_multiplayer_authority())
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has_method("_sync_loot_floating_text"):
|
||||
game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, item.spriteFrame, player.get_multiplayer_authority())
|
||||
|
||||
self.visible = false
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ signal player_connected(peer_id, player_info)
|
||||
signal player_disconnected(peer_id, player_info)
|
||||
signal connection_failed()
|
||||
signal connection_succeeded()
|
||||
signal rooms_fetched(rooms: Array) # Forwarded from room_registry
|
||||
|
||||
const DEFAULT_PORT = 21212
|
||||
const MAX_PLAYERS = 8
|
||||
@@ -24,17 +25,32 @@ var network_mode: int = 0 # 0 = ENet, 1 = WebRTC, 2 = WebSocket
|
||||
var room_id = "" # Room ID for Matchbox (WebRTC) or WebSocket server URL
|
||||
var matchbox_client: Node = null # Matchbox client instance
|
||||
const WEBSOCKET_SERVER_URL = "ws://ruinborn.thefirstboss.com:21212" # WebSocket server URL
|
||||
var room_registry: Node = null # Room registry client for room discovery
|
||||
|
||||
# Reconnection state (for joiners only)
|
||||
var reconnection_room_id: String = "" # Store room_id for reconnection
|
||||
var reconnection_level: int = 0 # Store level for reconnection
|
||||
var reconnection_attempting: bool = false # Track if we're attempting to reconnect
|
||||
var reconnection_timer: float = 0.0 # Timer for reconnection delay
|
||||
const RECONNECTION_DELAY: float = 2.0 # Delay before attempting reconnection
|
||||
|
||||
# Logging - use LogManager for categorized logging
|
||||
func log_print(message: String):
|
||||
LogManager.log(message, LogManager.CATEGORY_NETWORK)
|
||||
|
||||
func log_error(message: String):
|
||||
LogManager.log_error(message, LogManager.CATEGORY_NETWORK)
|
||||
|
||||
func _ready():
|
||||
# Detect if running in browser - default to WebRTC on web, ENet on native
|
||||
if OS.get_name() == "Web":
|
||||
network_mode = 1 # WebRTC default for web
|
||||
print("NetworkManager: Detected Web platform, defaulting to WebRTC")
|
||||
print("NetworkManager: Matchbox server: ", MATCHBOX_SERVER)
|
||||
log_print("NetworkManager: Detected Web platform, defaulting to WebRTC")
|
||||
log_print("NetworkManager: Matchbox server: " + MATCHBOX_SERVER)
|
||||
else:
|
||||
network_mode = 0 # ENet default for native
|
||||
print("NetworkManager: Using ENet by default for native platform")
|
||||
print("NetworkManager: You can switch network modes in the menu")
|
||||
log_print("NetworkManager: Using ENet by default for native platform")
|
||||
log_print("NetworkManager: You can switch network modes in the menu")
|
||||
|
||||
# Connect multiplayer signals
|
||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||
@@ -42,26 +58,29 @@ func _ready():
|
||||
multiplayer.connected_to_server.connect(_on_connected_to_server)
|
||||
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
# Create room registry client
|
||||
var registry_script = load("res://scripts/room_registry_client.gd")
|
||||
room_registry = Node.new()
|
||||
room_registry.set_script(registry_script)
|
||||
add_child(room_registry)
|
||||
room_registry.rooms_fetched.connect(_on_rooms_fetched)
|
||||
|
||||
func set_network_mode(mode: int):
|
||||
# 0 = ENet, 1 = WebRTC, 2 = WebSocket
|
||||
# WebRTC is only available on web builds
|
||||
if mode == 1 and OS.get_name() != "Web":
|
||||
push_error("NetworkManager: WebRTC is not available on native platforms. WebRTC only works in web builds. Falling back to ENet.")
|
||||
network_mode = 0
|
||||
print("NetworkManager: ENet mode enabled (WebRTC not available on native platforms)")
|
||||
return
|
||||
|
||||
# WebRTC is now available on native platforms with webrtc-native extension
|
||||
network_mode = mode
|
||||
match mode:
|
||||
0:
|
||||
print("NetworkManager: ENet mode enabled")
|
||||
log_print("NetworkManager: ENet mode enabled")
|
||||
1:
|
||||
print("NetworkManager: WebRTC mode enabled")
|
||||
print("NetworkManager: Matchbox server: ", MATCHBOX_SERVER)
|
||||
log_print("NetworkManager: WebRTC mode enabled")
|
||||
log_print("NetworkManager: Matchbox server: " + MATCHBOX_SERVER)
|
||||
if OS.get_name() != "Web":
|
||||
log_print("NetworkManager: Using webrtc-native extension for native platform")
|
||||
2:
|
||||
print("NetworkManager: WebSocket mode enabled")
|
||||
print("NetworkManager: WebSocket server: ", WEBSOCKET_SERVER_URL)
|
||||
log_print("NetworkManager: WebSocket mode enabled")
|
||||
log_print("NetworkManager: WebSocket server: " + WEBSOCKET_SERVER_URL)
|
||||
|
||||
func force_webrtc_mode(enable: bool):
|
||||
# Legacy function for backwards compatibility
|
||||
@@ -82,9 +101,9 @@ func host_game(port: int = DEFAULT_PORT, matchbox_room: String = "") -> bool:
|
||||
else:
|
||||
room_id = matchbox_room
|
||||
|
||||
print("NetworkManager: Creating WebRTC host with room ID: ", room_id)
|
||||
print("NetworkManager: Share this room code with players!")
|
||||
print("NetworkManager: Matchbox URL: ", MATCHBOX_SERVER, "/", room_id)
|
||||
log_print("NetworkManager: Creating WebRTC host with room ID: " + room_id)
|
||||
log_print("NetworkManager: Share this room code with players!")
|
||||
log_print("NetworkManager: Matchbox URL: " + MATCHBOX_SERVER + "/" + room_id)
|
||||
|
||||
# Create Matchbox client
|
||||
var matchbox_script = load("res://scripts/matchbox_client.gd")
|
||||
@@ -106,7 +125,7 @@ func host_game(port: int = DEFAULT_PORT, matchbox_room: String = "") -> bool:
|
||||
|
||||
# Connect to Matchbox room (pass is_hosting = true)
|
||||
if not matchbox_client.connect_to_room(room_id, true):
|
||||
push_error("Failed to connect to Matchbox room")
|
||||
log_error("Failed to connect to Matchbox room")
|
||||
return false
|
||||
|
||||
# Register the host as a player (peer_id 1)
|
||||
@@ -124,16 +143,16 @@ func host_game(port: int = DEFAULT_PORT, matchbox_room: String = "") -> bool:
|
||||
else:
|
||||
room_id = matchbox_room
|
||||
|
||||
print("NetworkManager: Creating WebSocket host with room ID: ", room_id)
|
||||
print("NetworkManager: Share this room code with players!")
|
||||
print("NetworkManager: WebSocket URL: ", WEBSOCKET_SERVER_URL, "/", room_id)
|
||||
log_print("NetworkManager: Creating WebSocket host with room ID: " + room_id)
|
||||
log_print("NetworkManager: Share this room code with players!")
|
||||
log_print("NetworkManager: WebSocket URL: " + WEBSOCKET_SERVER_URL + "/" + room_id)
|
||||
|
||||
peer = WebSocketMultiplayerPeer.new()
|
||||
var url = WEBSOCKET_SERVER_URL + "/" + room_id
|
||||
error = peer.create_server(port)
|
||||
|
||||
if error != OK:
|
||||
push_error("Failed to create WebSocket server: " + str(error))
|
||||
log_error("Failed to create WebSocket server: " + str(error))
|
||||
return false
|
||||
|
||||
multiplayer.multiplayer_peer = peer
|
||||
@@ -183,8 +202,8 @@ func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||
# 'address' is the room ID for WebRTC
|
||||
room_id = address
|
||||
|
||||
print("NetworkManager: Joining WebRTC game with room ID: ", room_id)
|
||||
print("NetworkManager: Matchbox URL: ", MATCHBOX_SERVER, "/", room_id)
|
||||
log_print("NetworkManager: Joining WebRTC game with room ID: " + room_id)
|
||||
log_print("NetworkManager: Matchbox URL: " + MATCHBOX_SERVER + "/" + room_id)
|
||||
|
||||
# Create Matchbox client
|
||||
var matchbox_script = load("res://scripts/matchbox_client.gd")
|
||||
@@ -204,7 +223,7 @@ func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||
|
||||
# Connect to Matchbox room (pass is_hosting = false)
|
||||
if not matchbox_client.connect_to_room(room_id, false):
|
||||
push_error("Failed to connect to Matchbox room")
|
||||
log_error("Failed to connect to Matchbox room")
|
||||
return false
|
||||
|
||||
return true
|
||||
@@ -213,21 +232,21 @@ func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||
# 'address' is the room ID for WebSocket
|
||||
room_id = address
|
||||
|
||||
print("NetworkManager: Joining WebSocket game with room ID: ", room_id)
|
||||
print("NetworkManager: WebSocket URL: ", WEBSOCKET_SERVER_URL, "/", room_id)
|
||||
log_print("NetworkManager: Joining WebSocket game with room ID: " + room_id)
|
||||
log_print("NetworkManager: WebSocket URL: " + WEBSOCKET_SERVER_URL + "/" + room_id)
|
||||
|
||||
peer = WebSocketMultiplayerPeer.new()
|
||||
var url = WEBSOCKET_SERVER_URL + "/" + room_id
|
||||
error = peer.create_client(url)
|
||||
|
||||
if error != OK:
|
||||
push_error("Failed to create WebSocket client: " + str(error))
|
||||
log_error("Failed to create WebSocket client: " + str(error))
|
||||
return false
|
||||
|
||||
multiplayer.multiplayer_peer = peer
|
||||
is_hosting = false
|
||||
|
||||
print("Attempting to connect to WebSocket server: ", url)
|
||||
log_print("Attempting to connect to WebSocket server: " + url)
|
||||
|
||||
return true
|
||||
else: # ENet (mode 0)
|
||||
@@ -236,17 +255,60 @@ func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||
error = peer.create_client(address, port)
|
||||
|
||||
if error != OK:
|
||||
push_error("Failed to create ENet client: " + str(error))
|
||||
log_error("Failed to create ENet client: " + str(error))
|
||||
return false
|
||||
|
||||
multiplayer.multiplayer_peer = peer
|
||||
is_hosting = false
|
||||
|
||||
print("Attempting to connect to ", address, ":", port)
|
||||
log_print("Attempting to connect to " + address + ":" + str(port))
|
||||
|
||||
return true
|
||||
|
||||
func refresh_webrtc_servers(room_override: String = "") -> bool:
|
||||
# Recreate Matchbox client to refresh WebRTC signaling connection
|
||||
if network_mode != 1:
|
||||
return false
|
||||
|
||||
var target_room = room_override if not room_override.is_empty() else room_id
|
||||
if target_room.is_empty():
|
||||
log_error("NetworkManager: Cannot refresh WebRTC servers - no room ID")
|
||||
return false
|
||||
|
||||
# Tear down existing client
|
||||
if matchbox_client:
|
||||
matchbox_client.disconnect_from_room()
|
||||
matchbox_client.queue_free()
|
||||
matchbox_client = null
|
||||
|
||||
# Recreate Matchbox client
|
||||
var matchbox_script = load("res://scripts/matchbox_client.gd")
|
||||
matchbox_client = Node.new()
|
||||
matchbox_client.set_script(matchbox_script)
|
||||
add_child(matchbox_client)
|
||||
|
||||
matchbox_client.peer_joined.connect(_on_matchbox_peer_joined)
|
||||
matchbox_client.peer_left.connect(_on_matchbox_peer_left)
|
||||
matchbox_client.peer_connected.connect(_on_matchbox_peer_connected)
|
||||
matchbox_client.connection_succeeded.connect(_on_matchbox_connected)
|
||||
matchbox_client.connection_failed.connect(_on_matchbox_connection_failed)
|
||||
matchbox_client.webrtc_ready.connect(_on_matchbox_webrtc_ready)
|
||||
|
||||
is_hosting = false
|
||||
room_id = target_room
|
||||
log_print("NetworkManager: Refreshing WebRTC servers for room ID: " + room_id)
|
||||
|
||||
if not matchbox_client.connect_to_room(room_id, false):
|
||||
log_error("NetworkManager: Failed to reconnect to Matchbox room during refresh")
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
func disconnect_from_game():
|
||||
# Unregister room from registry
|
||||
if room_registry:
|
||||
room_registry.unregister_room()
|
||||
|
||||
if matchbox_client:
|
||||
matchbox_client.disconnect_from_room()
|
||||
matchbox_client.queue_free()
|
||||
@@ -265,41 +327,118 @@ func set_local_player_count(count: int):
|
||||
func _generate_player_names(count: int, peer_id: int) -> Array:
|
||||
var names = []
|
||||
for i in range(count):
|
||||
names.append("Player%d_%d" % [peer_id, i + 1])
|
||||
if count == 1:
|
||||
# Only one player - don't add index suffix
|
||||
names.append("Player%d" % peer_id)
|
||||
else:
|
||||
# Multiple players - add index
|
||||
names.append("Player%d_%d" % [peer_id, i + 1])
|
||||
return names
|
||||
|
||||
# Called when a peer connects to the server
|
||||
func _on_peer_connected(id: int):
|
||||
print("Peer connected: ", id)
|
||||
log_print("Peer connected: " + str(id))
|
||||
# Peer is now actually available for RPCs - emit player_connected signal
|
||||
# Make sure player info is registered (should have been done in _on_matchbox_peer_connected)
|
||||
if not players_info.has(id):
|
||||
# Fallback: register default player info if not already registered
|
||||
players_info[id] = {
|
||||
"local_player_count": 1, # Default, will be updated via RPC
|
||||
"player_names": _generate_player_names(1, id)
|
||||
}
|
||||
|
||||
# For joiners, emit connection_succeeded when the first peer (host) connects
|
||||
# This ensures the connection is actually established and RPCs can be received
|
||||
# Note: On web, multiplayer.peer_connected might not fire automatically,
|
||||
# so we also check in _on_matchbox_peer_connected as a fallback
|
||||
if not is_hosting and id == 1: # Host connected to joiner
|
||||
log_print("NetworkManager: Joiner - host connected via multiplayer.peer_connected, emitting connection_succeeded")
|
||||
connection_succeeded.emit()
|
||||
|
||||
# Emit player connected signal (peer is now available for RPCs)
|
||||
player_connected.emit(id, players_info[id])
|
||||
|
||||
# Update room registry if hosting (player count changed)
|
||||
if is_hosting and room_registry and not room_id.is_empty():
|
||||
var player_count = get_all_player_ids().size()
|
||||
var _level = 1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
_level = game_world.current_level
|
||||
room_registry.send_room_update(player_count)
|
||||
|
||||
# Called when a peer disconnects
|
||||
func _on_peer_disconnected(id: int):
|
||||
print("Peer disconnected: ", id)
|
||||
log_print("Peer disconnected: " + str(id))
|
||||
# Get player_info before erasing it
|
||||
var player_info = {}
|
||||
if players_info.has(id):
|
||||
player_info = players_info[id]
|
||||
players_info.erase(id)
|
||||
player_disconnected.emit(id, player_info)
|
||||
|
||||
# Update room registry if hosting (player count changed)
|
||||
if is_hosting and room_registry and not room_id.is_empty():
|
||||
var player_count = get_all_player_ids().size()
|
||||
var _level = 1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
_level = game_world.current_level
|
||||
room_registry.send_room_update(player_count)
|
||||
|
||||
# Called on client when successfully connected to server
|
||||
func _on_connected_to_server():
|
||||
print("Successfully connected to server")
|
||||
log_print("Successfully connected to server")
|
||||
connection_succeeded.emit()
|
||||
|
||||
# Send our player info to the server
|
||||
var my_id = multiplayer.get_unique_id()
|
||||
_register_player.rpc_id(1, my_id, local_player_count)
|
||||
|
||||
# Update room registry if hosting (player count changed)
|
||||
if is_hosting and room_registry and not room_id.is_empty():
|
||||
var player_count = get_all_player_ids().size()
|
||||
var _level = 1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
_level = game_world.current_level
|
||||
room_registry.send_room_update(player_count)
|
||||
|
||||
# Called on client when connection fails
|
||||
func _on_connection_failed():
|
||||
print("Connection failed")
|
||||
log_print("Connection failed")
|
||||
multiplayer.multiplayer_peer = null
|
||||
connection_failed.emit()
|
||||
|
||||
# Called on client when disconnected from server
|
||||
func _on_server_disconnected():
|
||||
print("Server disconnected")
|
||||
log_print("Server disconnected")
|
||||
|
||||
# Store reconnection info if we're a joiner (not host)
|
||||
if not is_hosting and not room_id.is_empty():
|
||||
reconnection_room_id = room_id
|
||||
# Get current level from game_world
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and game_world.has("current_level"):
|
||||
reconnection_level = game_world.current_level
|
||||
log_print("NetworkManager: Stored reconnection info - room_id: " + reconnection_room_id + ", level: " + str(reconnection_level))
|
||||
else:
|
||||
reconnection_level = 0 # Unknown level
|
||||
|
||||
# Add local-only chat message "Connection lost..."
|
||||
if game_world:
|
||||
var chat_ui = game_world.get_node_or_null("ChatUI")
|
||||
if chat_ui and chat_ui.has_method("add_local_message"):
|
||||
chat_ui.add_local_message("System", "Connection lost...")
|
||||
|
||||
# Start reconnection attempt
|
||||
reconnection_attempting = true
|
||||
reconnection_timer = RECONNECTION_DELAY
|
||||
log_print("NetworkManager: Will attempt to reconnect in " + str(RECONNECTION_DELAY) + " seconds")
|
||||
|
||||
# Emit connection_failed signal to show UI message
|
||||
connection_failed.emit()
|
||||
|
||||
multiplayer.multiplayer_peer = null
|
||||
players_info.clear()
|
||||
|
||||
@@ -318,7 +457,8 @@ func _register_player(peer_id: int, local_count: int):
|
||||
print("NetworkManager: Total players_info: ", players_info)
|
||||
|
||||
# Sync all player info to the new client (so they know about everyone)
|
||||
_sync_players.rpc_id(peer_id, players_info)
|
||||
# Defer RPC to ensure the peer is fully registered in the multiplayer system
|
||||
call_deferred("_sync_players_to_peer", peer_id, players_info)
|
||||
|
||||
# Notify all clients (including the new one) about the new player
|
||||
_notify_player_joined.rpc(peer_id, players_info[peer_id])
|
||||
@@ -326,6 +466,11 @@ func _register_player(peer_id: int, local_count: int):
|
||||
# Emit signal on server
|
||||
player_connected.emit(peer_id, players_info[peer_id])
|
||||
|
||||
func _sync_players_to_peer(peer_id: int, all_players_info: Dictionary):
|
||||
# Helper function to send _sync_players RPC (called via call_deferred)
|
||||
if multiplayer.is_server():
|
||||
_sync_players.rpc_id(peer_id, all_players_info)
|
||||
|
||||
# RPC to sync all player info to a newly connected client
|
||||
@rpc("authority", "reliable")
|
||||
func _sync_players(all_players_info: Dictionary):
|
||||
@@ -353,32 +498,58 @@ func get_player_info(peer_id: int) -> Dictionary:
|
||||
return players_info.get(peer_id, {})
|
||||
|
||||
# Matchbox callback handlers
|
||||
func _on_matchbox_connected():
|
||||
print("NetworkManager: Connected to Matchbox server")
|
||||
func _on_matchbox_connected(was_reconnecting: bool = false):
|
||||
log_print("NetworkManager: Connected to Matchbox server")
|
||||
# WebRTC peer will be set up when Welcome message is received with our peer ID
|
||||
|
||||
# If host was reconnecting, show colorful reconnection message
|
||||
if is_hosting and was_reconnecting:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
var chat_ui = game_world.get_node_or_null("ChatUI")
|
||||
if chat_ui and chat_ui.has_method("add_colorful_local_message"):
|
||||
chat_ui.add_colorful_local_message("System", "Matchbox connection re-established!")
|
||||
|
||||
func _on_matchbox_webrtc_ready():
|
||||
print("NetworkManager: WebRTC mesh is ready")
|
||||
log_print("NetworkManager: WebRTC mesh is ready")
|
||||
|
||||
# Register ourselves if we're not hosting (clients need to register)
|
||||
if not is_hosting and matchbox_client:
|
||||
var my_peer_id = matchbox_client.get_my_peer_id()
|
||||
if my_peer_id > 0 and not players_info.has(my_peer_id):
|
||||
players_info[my_peer_id] = {
|
||||
"local_player_count": local_player_count,
|
||||
"player_names": _generate_player_names(local_player_count, my_peer_id)
|
||||
}
|
||||
print("NetworkManager: Registered joining player with peer ID: ", my_peer_id)
|
||||
|
||||
# Emit connection_succeeded signal so the game can start
|
||||
connection_succeeded.emit()
|
||||
# Register room with room registry if hosting
|
||||
if is_hosting and room_registry and not room_id.is_empty():
|
||||
var player_count = get_all_player_ids().size()
|
||||
var level = 1
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
level = game_world.current_level
|
||||
room_registry.register_room(room_id, player_count, level)
|
||||
log_print("NetworkManager: Registered room " + room_id + " with registry")
|
||||
|
||||
# Host can start immediately
|
||||
connection_succeeded.emit()
|
||||
else:
|
||||
# For joiners, wait for peer_connected to be emitted before starting
|
||||
# This ensures the connection is actually established and RPCs can be received
|
||||
log_print("NetworkManager: Joiner - waiting for peer connection before starting game")
|
||||
# connection_succeeded will be emitted in _on_peer_connected when the first peer (host) connects
|
||||
|
||||
func _on_matchbox_connection_failed():
|
||||
print("NetworkManager: Failed to connect to Matchbox server")
|
||||
connection_failed.emit()
|
||||
log_print("NetworkManager: Failed to connect to Matchbox server")
|
||||
|
||||
# If hosting, add local chat message and show UI
|
||||
if is_hosting:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
var chat_ui = game_world.get_node_or_null("ChatUI")
|
||||
if chat_ui and chat_ui.has_method("add_local_message"):
|
||||
chat_ui.add_local_message("System", "Lost connection to matchbox server...")
|
||||
|
||||
# Emit connection_failed to show UI message
|
||||
connection_failed.emit()
|
||||
else:
|
||||
# For joiners, just emit the signal (they already have their own handling)
|
||||
connection_failed.emit()
|
||||
|
||||
func _on_matchbox_peer_joined(peer_id: int):
|
||||
print("NetworkManager: Matchbox peer joined: ", peer_id)
|
||||
log_print("NetworkManager: Matchbox peer joined: " + str(peer_id))
|
||||
# Peer connection will be created by Matchbox client
|
||||
# Once connected, we'll add it to WebRTC mesh
|
||||
|
||||
@@ -388,23 +559,160 @@ func _on_matchbox_peer_left(peer_id: int):
|
||||
|
||||
func _on_matchbox_peer_connected(peer_id: int):
|
||||
print("NetworkManager: Matchbox peer connected: ", peer_id)
|
||||
# Add peer to WebRTC mesh
|
||||
if matchbox_client:
|
||||
matchbox_client.add_peer_to_mesh(peer_id)
|
||||
# Note: Peer connection is already added to WebRTC mesh in _create_peer_connection()
|
||||
# This signal is emitted after the answer is received, but the peer might not be available for RPCs yet
|
||||
# We'll emit player_connected from _on_peer_connected() when the peer is actually available
|
||||
|
||||
# Register player info
|
||||
# Register player info (we'll emit player_connected when multiplayer.peer_connected fires)
|
||||
if not players_info.has(peer_id):
|
||||
players_info[peer_id] = {
|
||||
"local_player_count": 1, # Default, will be updated via RPC
|
||||
"player_names": _generate_player_names(1, peer_id)
|
||||
}
|
||||
|
||||
# Emit player connected signal
|
||||
player_connected.emit(peer_id, players_info[peer_id])
|
||||
# For joiners, if this is the host (peer_id 1) and connection_succeeded hasn't been emitted yet,
|
||||
# wait a bit and then emit it as a fallback (in case multiplayer.peer_connected doesn't fire on web)
|
||||
if not is_hosting and peer_id == 1:
|
||||
# Check if we've already set up a timer for this peer (prevent multiple timers)
|
||||
if has_meta("connection_succeeded_timer_set"):
|
||||
log_print("NetworkManager: Joiner - timer already set for peer " + str(peer_id) + ", skipping")
|
||||
return
|
||||
|
||||
set_meta("connection_succeeded_timer_set", true)
|
||||
log_print("NetworkManager: Joiner - host connected via Matchbox, waiting for multiplayer.peer_connected...")
|
||||
# Store reference to scene tree before timer
|
||||
var scene_tree = get_tree()
|
||||
if not scene_tree:
|
||||
# Node is not in scene tree, emit immediately
|
||||
log_print("NetworkManager: Joiner - node not in scene tree, emitting connection_succeeded immediately")
|
||||
connection_succeeded.emit()
|
||||
return
|
||||
|
||||
# Wait a short time to see if multiplayer.peer_connected fires
|
||||
# If it doesn't fire within 2 seconds, emit connection_succeeded anyway
|
||||
var timer = scene_tree.create_timer(2.0)
|
||||
timer.timeout.connect(func():
|
||||
# Check if connection_succeeded was already emitted (by checking if we're in game scene)
|
||||
if not is_inside_tree():
|
||||
log_print("NetworkManager: Joiner - node no longer in tree, skipping fallback emit")
|
||||
return
|
||||
|
||||
var current_tree = get_tree()
|
||||
if not current_tree:
|
||||
log_print("NetworkManager: Joiner - no scene tree, skipping fallback emit")
|
||||
return
|
||||
|
||||
var game_world = current_tree.get_first_node_in_group("game_world")
|
||||
if not game_world:
|
||||
# Still in main menu, so connection_succeeded wasn't emitted
|
||||
log_print("NetworkManager: Joiner - multiplayer.peer_connected didn't fire, emitting connection_succeeded as fallback")
|
||||
# Emit signal immediately - NetworkManager is an autoload so it should always be in tree
|
||||
log_print("NetworkManager: is_inside_tree() = " + str(is_inside_tree()) + ", emitting connection_succeeded signal (fallback)")
|
||||
connection_succeeded.emit()
|
||||
log_print("NetworkManager: connection_succeeded signal emitted")
|
||||
|
||||
# Also try to get GameUI and call _start_game directly as a fallback
|
||||
# Search for GameUI by script (it has game_ui.gd script)
|
||||
var game_ui = null
|
||||
var root = current_tree.get_root()
|
||||
|
||||
# Try different possible paths (GameUI is at /root/GameUI)
|
||||
var possible_paths = [
|
||||
"/root/GameUI",
|
||||
"GameUI",
|
||||
"Main/GameUI",
|
||||
"/root/Main/GameUI"
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
var node = root.get_node_or_null(path)
|
||||
if node and node.get_script() and node.get_script().resource_path.ends_with("game_ui.gd"):
|
||||
game_ui = node
|
||||
log_print("NetworkManager: Found GameUI at path: " + path)
|
||||
break
|
||||
|
||||
# If not found by path, search all nodes recursively
|
||||
if not game_ui:
|
||||
game_ui = _find_node_by_script(root, "game_ui.gd")
|
||||
if game_ui:
|
||||
log_print("NetworkManager: Found GameUI by recursive script search: " + str(game_ui.get_path()))
|
||||
|
||||
if game_ui and game_ui.has_method("_start_game"):
|
||||
log_print("NetworkManager: Found GameUI node, calling _start_game directly as fallback")
|
||||
game_ui.call_deferred("_start_game")
|
||||
else:
|
||||
log_print("NetworkManager: GameUI node not found or doesn't have _start_game method")
|
||||
# Try to change scene directly as last resort
|
||||
var game_world_scene = load("res://scenes/game_world.tscn")
|
||||
if game_world_scene:
|
||||
log_print("NetworkManager: Attempting to change scene directly to game_world.tscn")
|
||||
current_tree.change_scene_to_packed(game_world_scene)
|
||||
else:
|
||||
log_print("NetworkManager: Joiner - already in game scene, connection_succeeded was already emitted")
|
||||
)
|
||||
|
||||
# Don't emit player_connected here - wait for multiplayer.peer_connected signal
|
||||
# which fires when the peer is actually available for RPCs
|
||||
|
||||
func _emit_connection_succeeded_safe():
|
||||
"""Safely emit connection_succeeded signal - checks if node is still valid"""
|
||||
if is_inside_tree():
|
||||
log_print("NetworkManager: Emitting connection_succeeded signal (fallback)")
|
||||
connection_succeeded.emit()
|
||||
else:
|
||||
log_print("NetworkManager: Cannot emit connection_succeeded - node not in tree")
|
||||
|
||||
func _find_node_by_script(node: Node, script_name: String) -> Node:
|
||||
"""Recursively search for a node with a specific script"""
|
||||
if node.get_script() and node.get_script().resource_path.ends_with(script_name):
|
||||
return node
|
||||
|
||||
for child in node.get_children():
|
||||
var result = _find_node_by_script(child, script_name)
|
||||
if result:
|
||||
return result
|
||||
|
||||
return null
|
||||
|
||||
func get_room_id() -> String:
|
||||
return room_id
|
||||
|
||||
func _process(delta: float):
|
||||
# Handle reconnection timer
|
||||
if reconnection_attempting:
|
||||
reconnection_timer -= delta
|
||||
if reconnection_timer <= 0.0:
|
||||
reconnection_attempting = false
|
||||
_attempt_reconnect()
|
||||
|
||||
func _attempt_reconnect():
|
||||
# Only reconnect if we're a joiner and have reconnection info
|
||||
if is_hosting or reconnection_room_id.is_empty():
|
||||
log_print("NetworkManager: Cannot reconnect - is_hosting: " + str(is_hosting) + ", reconnection_room_id: " + reconnection_room_id)
|
||||
return
|
||||
|
||||
log_print("NetworkManager: Attempting to reconnect to room: " + reconnection_room_id)
|
||||
|
||||
# Attempt to reconnect using the stored room_id
|
||||
var success = join_game(reconnection_room_id)
|
||||
if not success:
|
||||
log_error("NetworkManager: Reconnection attempt failed, will retry")
|
||||
# Retry after delay
|
||||
reconnection_attempting = true
|
||||
reconnection_timer = RECONNECTION_DELAY
|
||||
|
||||
func fetch_available_rooms() -> bool:
|
||||
"""Fetch available rooms from the registry"""
|
||||
if room_registry:
|
||||
return room_registry.fetch_available_rooms()
|
||||
return false
|
||||
|
||||
func _on_rooms_fetched(rooms: Array):
|
||||
"""Callback when rooms are fetched from registry"""
|
||||
log_print("NetworkManager: Received " + str(rooms.size()) + " available rooms")
|
||||
# Forward the signal to listeners
|
||||
rooms_fetched.emit(rooms)
|
||||
|
||||
func get_local_ip() -> String:
|
||||
var addresses = IP.get_local_addresses()
|
||||
for addr in addresses:
|
||||
|
||||
@@ -263,8 +263,6 @@ func _ready():
|
||||
_setup_player_appearance()
|
||||
|
||||
# Authority is set by player_manager after adding to scene
|
||||
# Just log it here
|
||||
print("Player ", name, " ready. Authority: ", get_multiplayer_authority(), " Is local: ", is_local_player)
|
||||
|
||||
# Hide interaction indicator by default
|
||||
if interaction_indicator:
|
||||
@@ -385,7 +383,6 @@ func _initialize_character_stats():
|
||||
appearance_rng = RandomNumberGenerator.new()
|
||||
var seed_value = hash(str(peer_id) + "_" + str(local_player_index))
|
||||
appearance_rng.seed = seed_value
|
||||
print(name, " appearance/stats seed: ", seed_value, " (peer_id: ", peer_id, ", local_index: ", local_player_index, ")")
|
||||
|
||||
# Create character stats
|
||||
character_stats = CharacterStats.new()
|
||||
@@ -415,13 +412,7 @@ func _randomize_stats():
|
||||
character_stats.baseStats.cha = appearance_rng.randi_range(8, 12)
|
||||
character_stats.baseStats.lck = appearance_rng.randi_range(8, 12)
|
||||
|
||||
print(name, " randomized stats: STR=", character_stats.baseStats.str,
|
||||
" DEX=", character_stats.baseStats.dex,
|
||||
" INT=", character_stats.baseStats.int,
|
||||
" END=", character_stats.baseStats.end,
|
||||
" WIS=", character_stats.baseStats.wis,
|
||||
" CHA=", character_stats.baseStats.cha,
|
||||
" LCK=", character_stats.baseStats.lck)
|
||||
# Stats randomized (verbose logging removed)
|
||||
|
||||
func _setup_player_appearance():
|
||||
# Randomize appearance - players spawn "bare" (naked, no equipment)
|
||||
@@ -600,10 +591,7 @@ func _apply_appearance_to_sprites():
|
||||
if sprite_weapon:
|
||||
sprite_weapon.texture = null # Weapons don't use character sprite layers
|
||||
|
||||
print(name, " appearance applied: skin=", character_stats.skin,
|
||||
" hair=", character_stats.hairstyle,
|
||||
" facial_hair=", character_stats.facial_hair,
|
||||
" eyes=", character_stats.eyes)
|
||||
# Appearance applied (verbose logging removed)
|
||||
|
||||
func _on_character_changed(_char: CharacterStats):
|
||||
# Update appearance when character stats change (e.g., equipment)
|
||||
@@ -619,7 +607,7 @@ func _on_character_changed(_char: CharacterStats):
|
||||
equipment_data[slot_name] = item.save() # Serialize item data
|
||||
else:
|
||||
equipment_data[slot_name] = null
|
||||
_sync_equipment.rpc(equipment_data)
|
||||
_rpc_to_ready_peers("_sync_equipment", [equipment_data])
|
||||
|
||||
# Sync equipment and inventory to client (when server adds/removes items for a client player)
|
||||
# This ensures joiners see items they pick up and equipment changes
|
||||
@@ -661,6 +649,88 @@ func _is_player(obj) -> bool:
|
||||
# Check if it's a player by looking for player-specific properties
|
||||
return obj.is_in_group("player") or ("is_local_player" in obj and "peer_id" in obj)
|
||||
|
||||
# Helper function to get consistent object name for network sync
|
||||
func _get_object_name_for_sync(obj) -> String:
|
||||
# For interactable objects, use the consistent name (InteractableObject_X)
|
||||
if obj.name.begins_with("InteractableObject_"):
|
||||
return obj.name
|
||||
if obj.has_meta("object_index"):
|
||||
var obj_index = obj.get_meta("object_index")
|
||||
return "InteractableObject_%d" % obj_index
|
||||
# For players, use their unique name
|
||||
if _is_player(obj):
|
||||
return obj.name
|
||||
# Last resort: use the node name (might be auto-generated like @CharacterBody2D@82)
|
||||
return obj.name
|
||||
|
||||
func _get_log_prefix() -> String:
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
return "[H] " if multiplayer.is_server() else "[J] "
|
||||
return ""
|
||||
|
||||
func _can_place_down_at(place_pos: Vector2, placed_obj: Node) -> bool:
|
||||
var space_state = get_world_2d().direct_space_state
|
||||
var placed_shape = _get_collision_shape_for(placed_obj)
|
||||
if not placed_shape:
|
||||
# Fallback to 16x16
|
||||
placed_shape = RectangleShape2D.new()
|
||||
placed_shape.size = Vector2(16, 16)
|
||||
|
||||
var params = PhysicsShapeQueryParameters2D.new()
|
||||
params.shape = placed_shape
|
||||
params.transform = Transform2D(0.0, place_pos)
|
||||
params.collision_mask = 1 | 2 | 64 # Players, objects, walls
|
||||
params.exclude = [self, placed_obj]
|
||||
|
||||
var hits = space_state.intersect_shape(params, 8)
|
||||
return hits.is_empty()
|
||||
|
||||
func _find_closest_place_pos(direction: Vector2, placed_obj: Node) -> Vector2:
|
||||
var dir = direction.normalized()
|
||||
if dir.length() < 0.1:
|
||||
dir = last_movement_direction.normalized()
|
||||
if dir.length() < 0.1:
|
||||
dir = Vector2.RIGHT
|
||||
|
||||
var player_extent = _get_collision_extent(self)
|
||||
var obj_extent = _get_collision_extent(placed_obj)
|
||||
# Start just outside player + object bounds
|
||||
var start_dist = max(8.0, player_extent + obj_extent + 1.0)
|
||||
var max_dist = start_dist + 32.0
|
||||
var step = 2.0
|
||||
|
||||
var best_pos = global_position + dir * max_dist
|
||||
for d in range(int(start_dist), int(max_dist) + 1, int(step)):
|
||||
var test_pos = global_position + dir * float(d)
|
||||
if _can_place_down_at(test_pos, placed_obj):
|
||||
return test_pos
|
||||
|
||||
return best_pos
|
||||
|
||||
func _get_collision_shape_for(node: Node) -> Shape2D:
|
||||
if not node:
|
||||
return null
|
||||
var shape_node = node.get_node_or_null("CollisionShape2D")
|
||||
if not shape_node:
|
||||
shape_node = node.find_child("CollisionShape2D", true, false)
|
||||
if shape_node and "shape" in shape_node:
|
||||
return shape_node.shape
|
||||
return null
|
||||
|
||||
func _get_collision_extent(node: Node) -> float:
|
||||
var shape = _get_collision_shape_for(node)
|
||||
if shape is RectangleShape2D:
|
||||
return max(shape.size.x, shape.size.y) * 0.5
|
||||
if shape is CapsuleShape2D:
|
||||
return shape.radius + shape.height * 0.5
|
||||
if shape is CircleShape2D:
|
||||
return shape.radius
|
||||
if shape is ConvexPolygonShape2D:
|
||||
var rect = shape.get_rect()
|
||||
return max(rect.size.x, rect.size.y) * 0.5
|
||||
# Fallback
|
||||
return 8.0
|
||||
|
||||
func _update_animation(delta):
|
||||
# Update animation frame timing
|
||||
time_since_last_frame += delta
|
||||
@@ -1031,15 +1101,10 @@ func _physics_process(delta):
|
||||
else:
|
||||
print("Player ", name, " (server) - all clients now ready! (no ready times tracked)")
|
||||
|
||||
# On server, also wait a bit after setting all_clients_ready to ensure nodes are registered
|
||||
if not multiplayer.is_server():
|
||||
_sync_position.rpc(position, velocity, position_z, is_airborne, current_direction, current_animation)
|
||||
elif all_clients_ready:
|
||||
# Wait an additional 0.2 seconds after setting all_clients_ready before sending RPCs
|
||||
var current_time = Time.get_ticks_msec() / 1000.0
|
||||
var time_since_ready = current_time - all_clients_ready_time
|
||||
if time_since_ready >= 0.2:
|
||||
_sync_position.rpc(position, velocity, position_z, is_airborne, current_direction, current_animation)
|
||||
# Sync position to all ready peers (clients and server)
|
||||
# Only send if node is still valid and in tree
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree() and is_instance_valid(self):
|
||||
_rpc_to_ready_peers("_sync_position", [position, velocity, position_z, is_airborne, current_direction, current_animation])
|
||||
|
||||
# Always move and slide to maintain horizontal velocity
|
||||
# When airborne, velocity is set by throw and decreases with friction
|
||||
@@ -1396,8 +1461,12 @@ func _handle_interactions():
|
||||
# Gamepad (X button)
|
||||
attack_just_pressed = Input.is_joy_button_pressed(input_device, JOY_BUTTON_X)
|
||||
|
||||
if attack_just_pressed and can_attack and not is_lifting and not is_pushing:
|
||||
_perform_attack()
|
||||
if attack_just_pressed and can_attack:
|
||||
if is_lifting:
|
||||
# Attack while lifting -> throw immediately (no movement required)
|
||||
_force_throw_held_object(last_movement_direction)
|
||||
elif not is_pushing:
|
||||
_perform_attack()
|
||||
|
||||
# Note: just_grabbed_this_frame is reset when we block a release, or stays true if we grabbed this frame
|
||||
# This ensures it persists to the next frame to block immediate release
|
||||
@@ -1475,9 +1544,11 @@ func _try_grab():
|
||||
|
||||
# Sync initial grab to network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_initial_grab.rpc(held_object.get_path(), grab_offset)
|
||||
# Use consistent object name or index instead of path
|
||||
var obj_name = _get_object_name_for_sync(held_object)
|
||||
_rpc_to_ready_peers("_sync_initial_grab", [obj_name, grab_offset])
|
||||
# Sync the grab state
|
||||
_sync_grab.rpc(held_object.get_path(), is_lifting, push_axis)
|
||||
_rpc_to_ready_peers("_sync_grab", [obj_name, is_lifting, push_axis])
|
||||
|
||||
print("Grabbed: ", closest_body.name)
|
||||
|
||||
@@ -1525,7 +1596,8 @@ func _lift_object():
|
||||
|
||||
# Sync to network (non-blocking)
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_grab.rpc(held_object.get_path(), true, push_axis)
|
||||
var obj_name = _get_object_name_for_sync(held_object)
|
||||
_rpc_to_ready_peers("_sync_grab", [obj_name, true, push_axis])
|
||||
|
||||
print("Lifted: ", held_object.name)
|
||||
$SfxLift.play()
|
||||
@@ -1565,7 +1637,8 @@ func _start_pushing():
|
||||
|
||||
# Sync push state to network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_grab.rpc(held_object.get_path(), false, push_axis) # false = pushing, not lifting
|
||||
var obj_name = _get_object_name_for_sync(held_object)
|
||||
_rpc_to_ready_peers("_sync_grab", [obj_name, false, push_axis]) # false = pushing, not lifting
|
||||
|
||||
print("Pushing: ", held_object.name, " along axis: ", push_axis, " facing dir: ", push_direction_locked)
|
||||
|
||||
@@ -1598,7 +1671,8 @@ func _stop_pushing():
|
||||
|
||||
# Sync release to network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_release.rpc(released_obj.get_path())
|
||||
var obj_name = _get_object_name_for_sync(released_obj)
|
||||
_rpc_to_ready_peers("_sync_release", [obj_name])
|
||||
|
||||
# Release the object and re-enable collision completely
|
||||
if _is_box(released_obj):
|
||||
@@ -1660,6 +1734,99 @@ func _throw_object():
|
||||
is_lifting = false
|
||||
is_pushing = false
|
||||
|
||||
# Re-enable collision completely
|
||||
if _is_box(thrown_obj):
|
||||
# Box: set position and physics first
|
||||
thrown_obj.global_position = throw_start_pos
|
||||
|
||||
# Set throw velocity for box (same force as player throw)
|
||||
if "throw_velocity" in thrown_obj:
|
||||
thrown_obj.throw_velocity = throw_direction * throw_force / thrown_obj.weight
|
||||
if "is_frozen" in thrown_obj:
|
||||
thrown_obj.is_frozen = false
|
||||
|
||||
# Make box airborne with same arc as players
|
||||
if "is_airborne" in thrown_obj:
|
||||
thrown_obj.is_airborne = true
|
||||
thrown_obj.position_z = 2.5
|
||||
thrown_obj.velocity_z = 100.0 # Scaled down for 1x scale
|
||||
|
||||
# Call on_thrown if available
|
||||
if thrown_obj.has_method("on_thrown"):
|
||||
thrown_obj.on_thrown(self, throw_direction * throw_force)
|
||||
|
||||
# ⚡ Delay collision re-enable to prevent self-collision
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
if thrown_obj and is_instance_valid(thrown_obj):
|
||||
thrown_obj.set_collision_layer_value(2, true)
|
||||
thrown_obj.set_collision_mask_value(1, true)
|
||||
thrown_obj.set_collision_mask_value(2, true)
|
||||
thrown_obj.set_collision_mask_value(7, true)
|
||||
elif _is_player(thrown_obj):
|
||||
# Player: set position and physics first
|
||||
thrown_obj.global_position = throw_start_pos
|
||||
|
||||
# Set horizontal velocity for the arc
|
||||
thrown_obj.velocity = throw_direction * throw_force * 0.8 # Slightly reduced for arc
|
||||
|
||||
# Make player airborne with Z velocity
|
||||
if "is_airborne" in thrown_obj:
|
||||
thrown_obj.is_airborne = true
|
||||
thrown_obj.position_z = 2.5 # Start slightly off ground
|
||||
thrown_obj.velocity_z = 100.0 # Scaled down for 1x scale
|
||||
|
||||
if thrown_obj.has_method("set_being_held"):
|
||||
thrown_obj.set_being_held(false)
|
||||
|
||||
# ⚡ Delay collision re-enable to prevent self-collision
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
if thrown_obj and is_instance_valid(thrown_obj):
|
||||
thrown_obj.set_collision_layer_value(1, true)
|
||||
thrown_obj.set_collision_mask_value(1, true)
|
||||
thrown_obj.set_collision_mask_value(7, true)
|
||||
|
||||
if thrown_obj.has_method("on_thrown"):
|
||||
thrown_obj.on_thrown(self, throw_direction * throw_force)
|
||||
|
||||
# Play throw animation
|
||||
_set_animation("THROW")
|
||||
$SfxThrow.play()
|
||||
|
||||
# Sync throw over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
var obj_name = _get_object_name_for_sync(thrown_obj)
|
||||
_rpc_to_ready_peers("_sync_throw", [obj_name, throw_start_pos, throw_direction * throw_force, name])
|
||||
|
||||
print("Threw ", thrown_obj.name, " from ", throw_start_pos, " with force: ", throw_direction * throw_force)
|
||||
|
||||
func _force_throw_held_object(direction: Vector2):
|
||||
if not held_object or not is_lifting:
|
||||
return
|
||||
|
||||
# Check if object can be thrown
|
||||
if held_object.has_method("can_be_thrown") and not held_object.can_be_thrown():
|
||||
# Can't throw this object, place it down instead
|
||||
_place_down_object()
|
||||
return
|
||||
|
||||
var throw_direction = direction.normalized()
|
||||
if throw_direction.length() < 0.1:
|
||||
throw_direction = last_movement_direction.normalized()
|
||||
if throw_direction.length() < 0.1:
|
||||
throw_direction = Vector2.RIGHT
|
||||
|
||||
# Position object at player's position before throwing
|
||||
var throw_start_pos = global_position + throw_direction * 10 # Start slightly in front
|
||||
|
||||
# Store reference before clearing
|
||||
var thrown_obj = held_object
|
||||
|
||||
# Clear state first (important!)
|
||||
held_object = null
|
||||
grab_offset = Vector2.ZERO
|
||||
is_lifting = false
|
||||
is_pushing = false
|
||||
|
||||
# Re-enable collision completely
|
||||
if _is_box(thrown_obj):
|
||||
# Box: set position and physics first
|
||||
@@ -1718,7 +1885,8 @@ func _throw_object():
|
||||
|
||||
# Sync throw over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_throw.rpc(thrown_obj.get_path(), throw_start_pos, throw_direction * throw_force, get_path())
|
||||
var obj_name = _get_object_name_for_sync(thrown_obj)
|
||||
_rpc_to_ready_peers("_sync_throw", [obj_name, throw_start_pos, throw_direction * throw_force, name])
|
||||
|
||||
print("Threw ", thrown_obj.name, " from ", throw_start_pos, " with force: ", throw_direction * throw_force)
|
||||
|
||||
@@ -1727,10 +1895,13 @@ func _place_down_object():
|
||||
return
|
||||
|
||||
# Place object in front of player based on last movement direction
|
||||
var place_offset = last_movement_direction * 15 # Scaled down for 1x scale
|
||||
var place_pos = global_position + place_offset
|
||||
var place_pos = _find_closest_place_pos(last_movement_direction, held_object)
|
||||
var placed_obj = held_object
|
||||
|
||||
if not _can_place_down_at(place_pos, placed_obj):
|
||||
print("DEBUG: Place down blocked - space not free")
|
||||
return
|
||||
|
||||
# Clear state
|
||||
held_object = null
|
||||
grab_offset = Vector2.ZERO
|
||||
@@ -1775,7 +1946,8 @@ func _place_down_object():
|
||||
|
||||
# Sync place down over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_place_down.rpc(placed_obj.get_path(), place_pos)
|
||||
var obj_name = _get_object_name_for_sync(placed_obj)
|
||||
_rpc_to_ready_peers("_sync_place_down", [obj_name, place_pos])
|
||||
|
||||
print("Placed down ", placed_obj.name, " at ", place_pos)
|
||||
|
||||
@@ -1893,7 +2065,7 @@ func _perform_attack():
|
||||
|
||||
# Sync attack over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_attack.rpc(current_direction, attack_direction)
|
||||
_rpc_to_ready_peers("_sync_attack", [current_direction, attack_direction])
|
||||
|
||||
# Reset attack cooldown (instant if cooldown is 0)
|
||||
if attack_cooldown > 0:
|
||||
@@ -1920,7 +2092,8 @@ func _update_lifted_object():
|
||||
|
||||
# Sync held object position over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_held_object_pos.rpc(held_object.get_path(), held_object.global_position)
|
||||
var obj_name = _get_object_name_for_sync(held_object)
|
||||
_rpc_to_ready_peers("_sync_held_object_pos", [obj_name, held_object.global_position])
|
||||
|
||||
func _update_pushed_object():
|
||||
if held_object and is_instance_valid(held_object):
|
||||
@@ -1963,10 +2136,10 @@ func _update_pushed_object():
|
||||
# Account for collision shape offset
|
||||
var shape_offset = held_collision_shape.position if held_collision_shape else Vector2.ZERO
|
||||
query.transform = Transform2D(0, target_pos + shape_offset)
|
||||
query.collision_mask = 64 # Layer 7 = walls (bit 6 = 64)
|
||||
query.collision_mask = 1 | 2 | 64 # Players, objects, walls
|
||||
query.collide_with_areas = false
|
||||
query.collide_with_bodies = true
|
||||
query.exclude = [held_object.get_rid()] # Exclude the object itself
|
||||
query.exclude = [held_object.get_rid(), get_rid()] # Exclude the object and the holder
|
||||
|
||||
var results = space_state.intersect_shape(query)
|
||||
was_blocked = results.size() > 0
|
||||
@@ -1976,11 +2149,11 @@ func _update_pushed_object():
|
||||
# Fallback: use point query
|
||||
var query = PhysicsPointQueryParameters2D.new()
|
||||
query.position = target_pos
|
||||
query.collision_mask = 64 # Layer 7 = walls (bit 6 = 64)
|
||||
query.collision_mask = 1 | 2 | 64 # Players, objects, walls
|
||||
query.collide_with_areas = false
|
||||
query.collide_with_bodies = true
|
||||
if held_object is CharacterBody2D:
|
||||
query.exclude = [held_object.get_rid()]
|
||||
query.exclude = [held_object.get_rid(), get_rid()]
|
||||
var results = space_state.intersect_point(query)
|
||||
was_blocked = results.size() > 0
|
||||
|
||||
@@ -2001,11 +2174,40 @@ func _update_pushed_object():
|
||||
|
||||
# Sync position over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_held_object_pos.rpc(held_object.get_path(), held_object.global_position)
|
||||
var obj_name = _get_object_name_for_sync(held_object)
|
||||
_rpc_to_ready_peers("_sync_held_object_pos", [obj_name, held_object.global_position])
|
||||
|
||||
# Send RPCs only to peers who are ready to receive them
|
||||
func _rpc_to_ready_peers(method: String, args: Array = []):
|
||||
if not multiplayer.has_multiplayer_peer():
|
||||
return
|
||||
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
# Server can use the ready-peer helper for safe fanout
|
||||
if multiplayer.is_server() and game_world and game_world.has_method("_rpc_node_to_ready_peers"):
|
||||
game_world._rpc_node_to_ready_peers(self, method, args)
|
||||
return
|
||||
|
||||
# Clients: only send to peers marked ready by server
|
||||
if game_world and "clients_ready" in game_world:
|
||||
for target_peer_id in multiplayer.get_peers():
|
||||
# Always allow sending to server (peer 1)
|
||||
if target_peer_id == 1:
|
||||
callv("rpc_id", [target_peer_id, method] + args)
|
||||
continue
|
||||
if game_world.clients_ready.has(target_peer_id) and game_world.clients_ready[target_peer_id]:
|
||||
callv("rpc_id", [target_peer_id, method] + args)
|
||||
else:
|
||||
# Fallback: send to all peers
|
||||
callv("rpc", [method] + args)
|
||||
|
||||
# Network sync
|
||||
@rpc("any_peer", "unreliable")
|
||||
func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, airborne: bool = false, dir: int = 0, anim: String = "IDLE"):
|
||||
# Check if node still exists and is valid before processing
|
||||
if not is_inside_tree() or not is_instance_valid(self):
|
||||
return
|
||||
|
||||
# Only update if we're not the authority (remote player)
|
||||
if not is_multiplayer_authority():
|
||||
position = pos
|
||||
@@ -2029,6 +2231,10 @@ func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, airborne: bo
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_attack(direction: int, attack_dir: Vector2):
|
||||
# Sync attack to other clients
|
||||
# Check if node still exists and is valid before processing
|
||||
if not is_inside_tree() or not is_instance_valid(self):
|
||||
return
|
||||
|
||||
if not is_multiplayer_authority():
|
||||
current_direction = direction as Direction
|
||||
_set_animation("SWORD")
|
||||
@@ -2036,6 +2242,10 @@ func _sync_attack(direction: int, attack_dir: Vector2):
|
||||
# Delay before spawning sword slash
|
||||
await get_tree().create_timer(0.15).timeout
|
||||
|
||||
# Check again after delay - node might have been destroyed
|
||||
if not is_inside_tree() or not is_instance_valid(self):
|
||||
return
|
||||
|
||||
# Spawn sword projectile on client
|
||||
if sword_projectile_scene:
|
||||
var projectile = sword_projectile_scene.instantiate()
|
||||
@@ -2048,11 +2258,36 @@ func _sync_attack(direction: int, attack_dir: Vector2):
|
||||
print(name, " performed synced attack!")
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_throw(obj_path: NodePath, throw_pos: Vector2, force: Vector2, thrower_path: NodePath):
|
||||
func _sync_throw(obj_name: String, throw_pos: Vector2, force: Vector2, thrower_name: String):
|
||||
# Sync throw to all clients (RPC sender already threw on their side)
|
||||
var obj = get_node_or_null(obj_path)
|
||||
var thrower = get_node_or_null(thrower_path)
|
||||
print("_sync_throw received: ", obj_path, " found obj: ", obj != null, " thrower: ", thrower != null, " is_authority: ", is_multiplayer_authority())
|
||||
# Check if node is still valid and in tree
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
# Find object by name (consistent name like InteractableObject_X)
|
||||
var obj = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world or not game_world.is_inside_tree():
|
||||
return # GameWorld not ready yet
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
obj = entities_node.get_node_or_null(obj_name)
|
||||
|
||||
# Fallback: if name lookup fails and name looks like InteractableObject_X, try object_index lookup
|
||||
if not obj and obj_name.begins_with("InteractableObject_") and entities_node:
|
||||
var index_str = obj_name.substr(20) # Skip "InteractableObject_"
|
||||
if index_str.is_valid_int():
|
||||
var obj_index = index_str.to_int()
|
||||
for child in entities_node.get_children():
|
||||
if child.has_meta("object_index") and child.get_meta("object_index") == obj_index:
|
||||
obj = child
|
||||
break
|
||||
|
||||
var thrower = null
|
||||
if entities_node:
|
||||
thrower = entities_node.get_node_or_null(thrower_name)
|
||||
print("_sync_throw received: ", obj_name, " found obj: ", obj != null, " thrower: ", thrower != null, " is_authority: ", is_multiplayer_authority())
|
||||
if obj:
|
||||
obj.global_position = throw_pos
|
||||
|
||||
@@ -2089,6 +2324,7 @@ func _sync_throw(obj_path: NodePath, throw_pos: Vector2, force: Vector2, thrower
|
||||
obj.set_collision_layer_value(2, true)
|
||||
obj.set_collision_mask_value(1, true)
|
||||
obj.set_collision_mask_value(2, true)
|
||||
obj.set_collision_mask_value(7, true)
|
||||
elif is_player:
|
||||
print("Syncing player throw on client! pos: ", throw_pos, " force: ", force)
|
||||
# Player: set physics first
|
||||
@@ -2109,12 +2345,36 @@ func _sync_throw(obj_path: NodePath, throw_pos: Vector2, force: Vector2, thrower
|
||||
if obj and is_instance_valid(obj):
|
||||
obj.set_collision_layer_value(1, true)
|
||||
obj.set_collision_mask_value(1, true)
|
||||
obj.set_collision_mask_value(7, true)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_initial_grab(obj_path: NodePath, _offset: Vector2):
|
||||
func _sync_initial_grab(obj_name: String, _offset: Vector2):
|
||||
# Sync initial grab to other clients
|
||||
# Check if node is still valid and in tree
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
if not is_multiplayer_authority():
|
||||
var obj = get_node_or_null(obj_path)
|
||||
# Find object by name (consistent name like InteractableObject_X)
|
||||
var obj = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world or not game_world.is_inside_tree():
|
||||
return # GameWorld not ready yet
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
obj = entities_node.get_node_or_null(obj_name)
|
||||
|
||||
# Fallback: if name lookup fails and name looks like InteractableObject_X, try object_index lookup
|
||||
if not obj and obj_name.begins_with("InteractableObject_"):
|
||||
var index_str = obj_name.substr(20) # Skip "InteractableObject_"
|
||||
if index_str.is_valid_int():
|
||||
var obj_index = index_str.to_int()
|
||||
for child in entities_node.get_children():
|
||||
if child.has_meta("object_index") and child.get_meta("object_index") == obj_index:
|
||||
obj = child
|
||||
break
|
||||
|
||||
if obj:
|
||||
# Disable collision for grabbed object
|
||||
if _is_box(obj):
|
||||
@@ -2125,13 +2385,36 @@ func _sync_initial_grab(obj_path: NodePath, _offset: Vector2):
|
||||
obj.set_collision_layer_value(1, false)
|
||||
obj.set_collision_mask_value(1, false)
|
||||
|
||||
print("Synced initial grab on client: ", obj_path)
|
||||
print("Synced initial grab on client: ", obj_name)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_grab(obj_path: NodePath, is_lift: bool, axis: Vector2 = Vector2.ZERO):
|
||||
func _sync_grab(obj_name: String, is_lift: bool, axis: Vector2 = Vector2.ZERO):
|
||||
# Sync lift/push state to other clients
|
||||
# Check if node is still valid and in tree
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
if not is_multiplayer_authority():
|
||||
var obj = get_node_or_null(obj_path)
|
||||
# Find object by name (consistent name like InteractableObject_X)
|
||||
var obj = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world or not game_world.is_inside_tree():
|
||||
return # GameWorld not ready yet
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
obj = entities_node.get_node_or_null(obj_name)
|
||||
|
||||
# Fallback: if name lookup fails and name looks like InteractableObject_X, try object_index lookup
|
||||
if not obj and obj_name.begins_with("InteractableObject_"):
|
||||
var index_str = obj_name.substr(20) # Skip "InteractableObject_"
|
||||
if index_str.is_valid_int():
|
||||
var obj_index = index_str.to_int()
|
||||
for child in entities_node.get_children():
|
||||
if child.has_meta("object_index") and child.get_meta("object_index") == obj_index:
|
||||
obj = child
|
||||
break
|
||||
|
||||
if obj:
|
||||
if is_lift:
|
||||
# Lifting - completely disable collision
|
||||
@@ -2174,10 +2457,33 @@ func _sync_grab(obj_path: NodePath, is_lift: bool, axis: Vector2 = Vector2.ZERO)
|
||||
print("Synced grab on client: lift=", is_lift, " axis=", axis)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_release(obj_path: NodePath):
|
||||
func _sync_release(obj_name: String):
|
||||
# Sync release to other clients
|
||||
# Check if node is still valid and in tree
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
if not is_multiplayer_authority():
|
||||
var obj = get_node_or_null(obj_path)
|
||||
# Find object by name (consistent name like InteractableObject_X)
|
||||
var obj = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world or not game_world.is_inside_tree():
|
||||
return # GameWorld not ready yet
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
obj = entities_node.get_node_or_null(obj_name)
|
||||
|
||||
# Fallback: if name lookup fails and name looks like InteractableObject_X, try object_index lookup
|
||||
if not obj and obj_name.begins_with("InteractableObject_"):
|
||||
var index_str = obj_name.substr(20) # Skip "InteractableObject_"
|
||||
if index_str.is_valid_int():
|
||||
var obj_index = index_str.to_int()
|
||||
for child in entities_node.get_children():
|
||||
if child.has_meta("object_index") and child.get_meta("object_index") == obj_index:
|
||||
obj = child
|
||||
break
|
||||
|
||||
if obj:
|
||||
# Re-enable collision completely
|
||||
if _is_box(obj):
|
||||
@@ -2198,10 +2504,33 @@ func _sync_release(obj_path: NodePath):
|
||||
obj.set_being_held(false)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_place_down(obj_path: NodePath, place_pos: Vector2):
|
||||
func _sync_place_down(obj_name: String, place_pos: Vector2):
|
||||
# Sync placing down to other clients
|
||||
# Check if node is still valid and in tree
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
if not is_multiplayer_authority():
|
||||
var obj = get_node_or_null(obj_path)
|
||||
# Find object by name (consistent name like InteractableObject_X)
|
||||
var obj = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world or not game_world.is_inside_tree():
|
||||
return # GameWorld not ready yet
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
obj = entities_node.get_node_or_null(obj_name)
|
||||
|
||||
# Fallback: if name lookup fails and name looks like InteractableObject_X, try object_index lookup
|
||||
if not obj and obj_name.begins_with("InteractableObject_"):
|
||||
var index_str = obj_name.substr(20) # Skip "InteractableObject_"
|
||||
if index_str.is_valid_int():
|
||||
var obj_index = index_str.to_int()
|
||||
for child in entities_node.get_children():
|
||||
if child.has_meta("object_index") and child.get_meta("object_index") == obj_index:
|
||||
obj = child
|
||||
break
|
||||
|
||||
if obj:
|
||||
obj.global_position = place_pos
|
||||
|
||||
@@ -2247,10 +2576,33 @@ func _sync_teleport_position(new_pos: Vector2):
|
||||
print(name, " teleported to ", new_pos, " (synced from server, is_authority: ", is_multiplayer_authority(), ")")
|
||||
|
||||
@rpc("any_peer", "unreliable")
|
||||
func _sync_held_object_pos(obj_path: NodePath, pos: Vector2):
|
||||
func _sync_held_object_pos(obj_name: String, pos: Vector2):
|
||||
# Sync held object position to other clients
|
||||
# Check if node is still valid and in tree
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
if not is_multiplayer_authority():
|
||||
var obj = get_node_or_null(obj_path)
|
||||
# Find object by name (consistent name like InteractableObject_X)
|
||||
var obj = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world or not game_world.is_inside_tree():
|
||||
return # GameWorld not ready yet
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
obj = entities_node.get_node_or_null(obj_name)
|
||||
|
||||
# Fallback: if name lookup fails and name looks like InteractableObject_X, try object_index lookup
|
||||
if not obj and obj_name.begins_with("InteractableObject_"):
|
||||
var index_str = obj_name.substr(20) # Skip "InteractableObject_"
|
||||
if index_str.is_valid_int():
|
||||
var obj_index = index_str.to_int()
|
||||
for child in entities_node.get_children():
|
||||
if child.has_meta("object_index") and child.get_meta("object_index") == obj_index:
|
||||
obj = child
|
||||
break
|
||||
|
||||
if obj:
|
||||
# Don't update position if object is airborne (being thrown)
|
||||
if "is_airborne" in obj and obj.is_airborne:
|
||||
@@ -2313,28 +2665,34 @@ func _break_free_from_holder():
|
||||
|
||||
# Sync break free over network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_break_free.rpc(being_held_by.get_path(), struggle_direction)
|
||||
_rpc_to_ready_peers("_sync_break_free", [being_held_by.name, struggle_direction])
|
||||
|
||||
struggle_time = 0.0
|
||||
struggle_direction = Vector2.ZERO
|
||||
being_held_by = null
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_break_free(holder_path: NodePath, direction: Vector2):
|
||||
var holder = get_node_or_null(holder_path)
|
||||
func _sync_break_free(holder_name: String, direction: Vector2):
|
||||
var holder = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
holder = entities_node.get_node_or_null(holder_name)
|
||||
|
||||
if holder and holder.has_method("_force_place_down"):
|
||||
holder._force_place_down(direction)
|
||||
|
||||
func _force_place_down(direction: Vector2):
|
||||
# Forced to place down held object in specified direction
|
||||
if held_object and is_lifting:
|
||||
var place_offset = direction.normalized() * 20
|
||||
if place_offset.length() < 0.1:
|
||||
place_offset = last_movement_direction * 20
|
||||
|
||||
var place_pos = position + place_offset
|
||||
var place_pos = _find_closest_place_pos(direction, held_object)
|
||||
var placed_obj = held_object
|
||||
|
||||
if not _can_place_down_at(place_pos, placed_obj):
|
||||
print("DEBUG: Forced place down blocked - space not free")
|
||||
return
|
||||
|
||||
# Clear state
|
||||
held_object = null
|
||||
grab_offset = Vector2.ZERO
|
||||
@@ -2414,9 +2772,17 @@ func take_damage(amount: float, attacker_position: Vector2):
|
||||
_show_damage_number(0.0, attacker_position, false, false, true) # is_dodged = true
|
||||
# Sync dodge visual to other clients
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_sync_damage.rpc(0.0, attacker_position, false, false, true) # is_dodged = true
|
||||
_rpc_to_ready_peers("_sync_damage", [0.0, attacker_position, false, false, true]) # is_dodged = true
|
||||
return # No damage taken, exit early
|
||||
|
||||
# If taking damage while holding something, drop/throw immediately
|
||||
if held_object:
|
||||
if is_lifting:
|
||||
var throw_dir = (global_position - attacker_position).normalized()
|
||||
_force_throw_held_object(throw_dir)
|
||||
else:
|
||||
_stop_pushing()
|
||||
|
||||
# If not dodged, apply damage with DEF reduction
|
||||
var actual_damage = amount
|
||||
if character_stats:
|
||||
@@ -2466,7 +2832,7 @@ func take_damage(amount: float, attacker_position: Vector2):
|
||||
|
||||
# Sync damage visual effects to other clients (including damage numbers)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_sync_damage.rpc(actual_damage, attacker_position)
|
||||
_rpc_to_ready_peers("_sync_damage", [actual_damage, attacker_position])
|
||||
|
||||
# Check if dead - but wait for damage animation to play first
|
||||
var health = character_stats.hp if character_stats else current_health
|
||||
@@ -2517,7 +2883,8 @@ func _die():
|
||||
|
||||
# Sync release to network
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_release.rpc(released_obj.get_path())
|
||||
var obj_name = _get_object_name_for_sync(released_obj)
|
||||
_rpc_to_ready_peers("_sync_release", [obj_name])
|
||||
|
||||
print(name, " released ", released_obj.name, " on death")
|
||||
else:
|
||||
@@ -2548,7 +2915,7 @@ func _die():
|
||||
|
||||
# Sync death over network (only authority sends)
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_death.rpc()
|
||||
_rpc_to_ready_peers("_sync_death", [])
|
||||
|
||||
# Wait for DIE animation to complete (200+200+200+800 = 1400ms = 1.4s)
|
||||
await get_tree().create_timer(1.4).timeout
|
||||
@@ -2586,7 +2953,7 @@ func _die():
|
||||
|
||||
# THEN sync to other clients
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_force_holder_to_drop.rpc(other_player.get_path())
|
||||
_rpc_to_ready_peers("_force_holder_to_drop", [other_player.name])
|
||||
|
||||
found_holder = true
|
||||
break
|
||||
@@ -2709,17 +3076,22 @@ func _respawn():
|
||||
|
||||
# Sync respawn over network (only authority sends)
|
||||
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||
_sync_respawn.rpc(new_respawn_pos)
|
||||
_rpc_to_ready_peers("_sync_respawn", [new_respawn_pos])
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _force_holder_to_drop(holder_path: NodePath):
|
||||
func _force_holder_to_drop(holder_name: String):
|
||||
# Force a specific player to drop what they're holding
|
||||
_force_holder_to_drop_local(holder_path)
|
||||
_force_holder_to_drop_local(holder_name)
|
||||
|
||||
func _force_holder_to_drop_local(holder_path: NodePath):
|
||||
func _force_holder_to_drop_local(holder_name: String):
|
||||
# Local function to clear holder's held object
|
||||
print("_force_holder_to_drop_local called for holder path: ", holder_path)
|
||||
var holder = get_node_or_null(holder_path)
|
||||
print("_force_holder_to_drop_local called for holder: ", holder_name)
|
||||
var holder = null
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
holder = entities_node.get_node_or_null(holder_name)
|
||||
if holder and is_instance_valid(holder):
|
||||
print(" Found holder: ", holder.name, ", their held_object: ", holder.held_object)
|
||||
if holder.held_object == self:
|
||||
@@ -2863,6 +3235,12 @@ func heal(amount: float):
|
||||
func add_key(amount: int = 1):
|
||||
keys += amount
|
||||
print(name, " picked up ", amount, " key(s)! Total keys: ", keys)
|
||||
|
||||
# Sync key count to owning client (server authoritative)
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
||||
var owner_peer_id = get_multiplayer_authority()
|
||||
if owner_peer_id != 0 and owner_peer_id != multiplayer.get_unique_id():
|
||||
_sync_keys.rpc_id(owner_peer_id, keys)
|
||||
|
||||
func has_key() -> bool:
|
||||
return keys > 0
|
||||
@@ -2870,10 +3248,17 @@ func has_key() -> bool:
|
||||
func use_key():
|
||||
if keys > 0:
|
||||
keys -= 1
|
||||
print(name, " used a key! Remaining keys: ", keys)
|
||||
print(_get_log_prefix(), name, " used a key! Remaining keys: ", keys)
|
||||
return true
|
||||
return false
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_keys(new_key_count: int):
|
||||
# Sync key count to client
|
||||
if not is_inside_tree():
|
||||
return
|
||||
keys = new_key_count
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false):
|
||||
# Show damage number (red, using dmg_numbers.png font) above player
|
||||
|
||||
@@ -81,11 +81,8 @@ func spawn_player(peer_id: int, local_index: int):
|
||||
# Set multiplayer authority AFTER adding to scene
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
player.set_multiplayer_authority(peer_id)
|
||||
print("Set authority for player ", unique_id, " to peer ", peer_id)
|
||||
|
||||
players[unique_id] = player
|
||||
|
||||
print("Spawned player: ", unique_id, " at ", player.position, " (local: ", player.is_local_player, ")")
|
||||
|
||||
func despawn_players_for_peer(peer_id: int):
|
||||
var to_remove = []
|
||||
|
||||
226
src/scripts/room_registry_client.gd
Normal file
226
src/scripts/room_registry_client.gd
Normal file
@@ -0,0 +1,226 @@
|
||||
extends Node
|
||||
|
||||
# Room Registry Client - Handles HTTP requests to room discovery server
|
||||
# Allows hosts to register rooms and joiners to discover available rooms
|
||||
|
||||
# Logging - use LogManager for categorized logging
|
||||
func log_print(message: String):
|
||||
LogManager.log(message, LogManager.CATEGORY_NETWORK)
|
||||
|
||||
func log_error(message: String):
|
||||
LogManager.log_error(message, LogManager.CATEGORY_NETWORK)
|
||||
|
||||
signal rooms_fetched(rooms: Array)
|
||||
signal room_update_sent(success: bool)
|
||||
|
||||
const ROOM_LIST_URL = "https://ruinborn-rooms.thefirstboss.com/index.php"
|
||||
const ROOM_UPDATE_URL = "https://ruinborn-rooms.thefirstboss.com/update.php"
|
||||
|
||||
var http_request_list: HTTPRequest = null
|
||||
var http_request_update: HTTPRequest = null
|
||||
var update_timer: Timer = null
|
||||
var current_room: String = ""
|
||||
var current_level: int = 1
|
||||
var pending_update: Dictionary = {} # Store pending update (player_count, level) when request is in progress
|
||||
var is_update_requesting: bool = false # Flag to track if an update request is in progress
|
||||
var is_list_requesting: bool = false # Flag to track if a room list request is in progress
|
||||
|
||||
func _ready():
|
||||
# Create HTTPRequest nodes for fetching and updating
|
||||
http_request_list = HTTPRequest.new()
|
||||
http_request_list.name = "HTTPRequestList"
|
||||
add_child(http_request_list)
|
||||
http_request_list.request_completed.connect(_on_room_list_received)
|
||||
|
||||
http_request_update = HTTPRequest.new()
|
||||
http_request_update.name = "HTTPRequestUpdate"
|
||||
add_child(http_request_update)
|
||||
http_request_update.request_completed.connect(_on_room_update_received)
|
||||
|
||||
# Create timer for periodic updates
|
||||
update_timer = Timer.new()
|
||||
update_timer.name = "UpdateTimer"
|
||||
update_timer.wait_time = 15.0 # Update every 15 seconds
|
||||
update_timer.timeout.connect(_on_update_timer_timeout)
|
||||
update_timer.autostart = false
|
||||
add_child(update_timer)
|
||||
|
||||
func fetch_available_rooms() -> bool:
|
||||
"""Fetch list of available rooms from the server"""
|
||||
# Check if a request is already in progress
|
||||
if is_list_requesting or http_request_list.get_http_client_status() == HTTPClient.STATUS_REQUESTING:
|
||||
log_print("RoomRegistry: Room list request already in progress, skipping duplicate request")
|
||||
return false
|
||||
|
||||
# Mark that we're starting a request
|
||||
is_list_requesting = true
|
||||
|
||||
log_print("RoomRegistry: Fetching available rooms...")
|
||||
var error = http_request_list.request(ROOM_LIST_URL)
|
||||
if error != OK:
|
||||
log_error("RoomRegistry: Failed to request room list: " + str(error))
|
||||
is_list_requesting = false # Reset flag on error
|
||||
return false
|
||||
return true
|
||||
|
||||
func register_room(room_code: String, player_count: int, level: int) -> bool:
|
||||
"""Register a room and start sending keepalive updates"""
|
||||
current_room = room_code
|
||||
current_level = level
|
||||
log_print("RoomRegistry: Registering room: " + room_code + " with " + str(player_count) + " players, level " + str(level))
|
||||
|
||||
# Send initial update
|
||||
send_room_update(player_count)
|
||||
|
||||
# Start periodic updates
|
||||
update_timer.start()
|
||||
|
||||
return true
|
||||
|
||||
func send_room_update(player_count: int, level: int = -1) -> bool:
|
||||
"""Send a keepalive update for the current room"""
|
||||
if current_room.is_empty():
|
||||
return false
|
||||
|
||||
# Get level from game_world if not provided
|
||||
if level < 0:
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
level = game_world.current_level
|
||||
else:
|
||||
level = current_level
|
||||
else:
|
||||
current_level = level
|
||||
|
||||
# Check if a request is already in progress - if so, queue this update
|
||||
# HTTPRequest can only handle one request at a time
|
||||
if is_update_requesting or http_request_update.get_http_client_status() == HTTPClient.STATUS_REQUESTING:
|
||||
log_print("RoomRegistry: Update request already in progress, queuing update")
|
||||
pending_update = {
|
||||
"player_count": player_count,
|
||||
"level": level
|
||||
}
|
||||
return true # Return true to indicate update is queued
|
||||
|
||||
# Clear any pending update since we're sending now
|
||||
pending_update = {}
|
||||
|
||||
# Mark that we're starting a request
|
||||
is_update_requesting = true
|
||||
|
||||
var json_data = {
|
||||
"data": {
|
||||
"room": current_room,
|
||||
"players": player_count,
|
||||
"level": level
|
||||
}
|
||||
}
|
||||
|
||||
var json_string = JSON.stringify(json_data)
|
||||
var headers = ["Content-Type: application/json"]
|
||||
|
||||
log_print("RoomRegistry: Sending room update: " + json_string)
|
||||
var error = http_request_update.request(ROOM_UPDATE_URL, headers, HTTPClient.METHOD_POST, json_string)
|
||||
if error != OK:
|
||||
log_error("RoomRegistry: Failed to send room update: " + str(error))
|
||||
is_update_requesting = false # Reset flag on error
|
||||
return false
|
||||
return true
|
||||
|
||||
func unregister_room():
|
||||
"""Stop sending updates and clear room registration"""
|
||||
log_print("RoomRegistry: Unregistering room: " + current_room)
|
||||
update_timer.stop()
|
||||
current_room = ""
|
||||
current_level = 1
|
||||
|
||||
func _on_room_list_received(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray):
|
||||
"""Handle response from room list request"""
|
||||
# Reset the requesting flag
|
||||
is_list_requesting = false
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
log_error("RoomRegistry: Failed to fetch rooms: HTTP result " + str(result))
|
||||
rooms_fetched.emit([])
|
||||
return
|
||||
|
||||
if response_code != 200:
|
||||
log_error("RoomRegistry: HTTP error code: " + str(response_code))
|
||||
rooms_fetched.emit([])
|
||||
return
|
||||
|
||||
var json = JSON.new()
|
||||
var parse_error = json.parse(body.get_string_from_utf8())
|
||||
if parse_error != OK:
|
||||
log_error("RoomRegistry: Failed to parse room list JSON: " + str(parse_error))
|
||||
rooms_fetched.emit([])
|
||||
return
|
||||
|
||||
var data = json.data
|
||||
if not data.has("status") or data["status"] != "OK":
|
||||
log_error("RoomRegistry: Server returned error status")
|
||||
rooms_fetched.emit([])
|
||||
return
|
||||
|
||||
var rooms = []
|
||||
if data.has("data") and data["data"] is Array:
|
||||
for room_data in data["data"]:
|
||||
if room_data.has("room"):
|
||||
rooms.append({
|
||||
"room": room_data["room"],
|
||||
"players": room_data.get("players", 0),
|
||||
"level": room_data.get("level", 1)
|
||||
})
|
||||
|
||||
log_print("RoomRegistry: Found " + str(rooms.size()) + " available rooms")
|
||||
rooms_fetched.emit(rooms)
|
||||
|
||||
func _on_room_update_received(result: int, response_code: int, _headers: PackedStringArray, _body: PackedByteArray):
|
||||
"""Handle response from room update request"""
|
||||
# Reset the requesting flag
|
||||
is_update_requesting = false
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
log_error("RoomRegistry: Failed to send room update: HTTP result " + str(result))
|
||||
room_update_sent.emit(false)
|
||||
# Try to send pending update if any
|
||||
if not pending_update.is_empty():
|
||||
send_room_update(pending_update.get("player_count", 0), pending_update.get("level", -1))
|
||||
return
|
||||
|
||||
if response_code != 200:
|
||||
log_error("RoomRegistry: Room update HTTP error code: " + str(response_code))
|
||||
room_update_sent.emit(false)
|
||||
# Try to send pending update if any
|
||||
if not pending_update.is_empty():
|
||||
send_room_update(pending_update.get("player_count", 0), pending_update.get("level", -1))
|
||||
return
|
||||
|
||||
log_print("RoomRegistry: Room update sent successfully")
|
||||
room_update_sent.emit(true)
|
||||
|
||||
# Send pending update if any
|
||||
if not pending_update.is_empty():
|
||||
var queued_player_count = pending_update.get("player_count", 0)
|
||||
var queued_level = pending_update.get("level", -1)
|
||||
pending_update = {} # Clear pending update before sending
|
||||
send_room_update(queued_player_count, queued_level)
|
||||
|
||||
func _on_update_timer_timeout():
|
||||
"""Periodic timer callback to send keepalive updates"""
|
||||
if current_room.is_empty():
|
||||
return
|
||||
|
||||
# Get current player count from NetworkManager
|
||||
var network_manager = get_node("/root/NetworkManager")
|
||||
if not network_manager:
|
||||
return
|
||||
|
||||
var player_count = network_manager.get_all_player_ids().size()
|
||||
var level = current_level
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world:
|
||||
level = game_world.current_level
|
||||
current_level = level
|
||||
|
||||
send_room_update(player_count, level)
|
||||
1
src/scripts/room_registry_client.gd.uid
Normal file
1
src/scripts/room_registry_client.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b165x5g8k5iwd
|
||||
@@ -45,9 +45,9 @@ func _on_body_exited(body):
|
||||
|
||||
func _on_player_entered_room(player: Node):
|
||||
# Handle player entering room
|
||||
print("Player ", player.name, " entered room at ", room.x, ", ", room.y)
|
||||
print("RoomTrigger: This trigger is for room (", room.x, ", ", room.y, ", ", room.w, "x", room.h, ")")
|
||||
print("RoomTrigger: Found ", doors_in_room.size(), " doors in this room")
|
||||
LogManager.log("Player " + str(player.name) + " entered room at " + str(room.x) + ", " + str(room.y), LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: This trigger is for room (" + str(room.x) + ", " + str(room.y) + ", " + str(room.w) + "x" + str(room.h) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Found " + str(doors_in_room.size()) + " doors in this room", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Mark room as entered and update debug label
|
||||
room_entered = true
|
||||
@@ -78,10 +78,14 @@ func _on_player_entered_room(player: Node):
|
||||
|
||||
if not door_in_this_room:
|
||||
# Door is NOT in this room - DO NOT call it!
|
||||
print("RoomTrigger: ERROR - Door ", door.name, " is NOT in room (", room.x, ", ", room.y, ")!")
|
||||
print("RoomTrigger: Door room1: (", door_room1.x if door_room1 else "none", ", ", door_room1.y if door_room1 else "none", ")")
|
||||
print("RoomTrigger: Door blocking_room: (", door_blocking_room.x if door_blocking_room else "none", ", ", door_blocking_room.y if door_blocking_room else "none", ")")
|
||||
print("RoomTrigger: Removing from this trigger's doors list!")
|
||||
var door_room1_x = str(door_room1.x) if door_room1 and not door_room1.is_empty() else "none"
|
||||
var door_room1_y = str(door_room1.y) if door_room1 and not door_room1.is_empty() else "none"
|
||||
var door_blocking_room_x = str(door_blocking_room.x) if door_blocking_room and not door_blocking_room.is_empty() else "none"
|
||||
var door_blocking_room_y = str(door_blocking_room.y) if door_blocking_room and not door_blocking_room.is_empty() else "none"
|
||||
LogManager.log_error("RoomTrigger: ERROR - Door " + str(door.name) + " is NOT in room (" + str(room.x) + ", " + str(room.y) + ")!", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Door room1: (" + door_room1_x + ", " + door_room1_y + ")", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Door blocking_room: (" + door_blocking_room_x + ", " + door_blocking_room_y + ")", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Removing from this trigger's doors list!", LogManager.CATEGORY_DUNGEON)
|
||||
doors_in_room.erase(door)
|
||||
if door.room_trigger_area == self:
|
||||
door.room_trigger_area = null
|
||||
@@ -96,13 +100,13 @@ func _on_player_entered_room(player: Node):
|
||||
door._on_room_entered(player)
|
||||
|
||||
# Spawn enemies if this room has a spawner
|
||||
print("RoomTrigger: About to call _spawn_room_enemies()...")
|
||||
LogManager.log("RoomTrigger: About to call _spawn_room_enemies()...", LogManager.CATEGORY_DUNGEON)
|
||||
_spawn_room_enemies()
|
||||
print("RoomTrigger: Finished _spawn_room_enemies()")
|
||||
LogManager.log("RoomTrigger: Finished _spawn_room_enemies()", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
func _on_player_exited_room(player: Node):
|
||||
# Handle player leaving room
|
||||
print("Player ", player.name, " exited room at ", room.x, ", ", room.y)
|
||||
LogManager.log("Player " + str(player.name) + " exited room at " + str(room.x) + ", " + str(room.y), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
func _find_room_entities():
|
||||
# Find all doors, enemies, and switches that belong to this room
|
||||
@@ -118,13 +122,13 @@ func _find_room_entities():
|
||||
# CRITICAL: Find doors where room1 == THIS room OR blocking_room == THIS room
|
||||
# Blocking doors are IN the puzzle room (they lead OUT OF this room)
|
||||
# When you enter this room, doors IN this room should close (blocking exits)
|
||||
print("RoomTrigger: Finding doors for room (", room.x, ", ", room.y, ", ", room.w, "x", room.h, ")")
|
||||
LogManager.log("RoomTrigger: Finding doors for room (" + str(room.x) + ", " + str(room.y) + ", " + str(room.w) + "x" + str(room.h) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
var _total_blocking_doors = 0
|
||||
for child in entities_node.get_children():
|
||||
if child.is_in_group("blocking_door") or child.name.begins_with("BlockingDoor_"):
|
||||
_total_blocking_doors += 1
|
||||
if not is_instance_valid(child):
|
||||
print("RoomTrigger: Door ", child.name, " is invalid, skipping")
|
||||
LogManager.log("RoomTrigger: Door " + str(child.name) + " is invalid, skipping", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# Check if door is IN this room (room1 == this room OR blocking_room == this room)
|
||||
@@ -142,7 +146,7 @@ func _find_room_entities():
|
||||
door_room1.w == room.w and door_room1.h == room.h)
|
||||
if room1_matches:
|
||||
door_in_this_room = true
|
||||
print("RoomTrigger: Door ", child.name, " room1 matches this room (door IN this room)")
|
||||
LogManager.log("RoomTrigger: Door " + str(child.name) + " room1 matches this room (door IN this room)", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# For blocking doors, also verify blocking_room matches (it should match room1)
|
||||
if door_in_this_room and is_blocking_door and door_blocking_room and not door_blocking_room.is_empty():
|
||||
@@ -150,22 +154,24 @@ func _find_room_entities():
|
||||
door_blocking_room.w == room.w and door_blocking_room.h == room.h)
|
||||
if not blocking_matches:
|
||||
# Blocking door's blocking_room doesn't match - this is an error!
|
||||
print("RoomTrigger: ERROR - Blocking door ", child.name, " room1 matches but blocking_room (", door_blocking_room.x, ", ", door_blocking_room.y, ") doesn't match this room (", room.x, ", ", room.y, ")!")
|
||||
LogManager.log_error("RoomTrigger: ERROR - Blocking door " + str(child.name) + " room1 matches but blocking_room (" + str(door_blocking_room.x) + ", " + str(door_blocking_room.y) + ") doesn't match this room (" + str(room.x) + ", " + str(room.y) + ")!", LogManager.CATEGORY_DUNGEON)
|
||||
door_in_this_room = false # Reject this door
|
||||
else:
|
||||
print("RoomTrigger: Blocking door ", child.name, " blocking_room also matches this room (verified)")
|
||||
LogManager.log("RoomTrigger: Blocking door " + str(child.name) + " blocking_room also matches this room (verified)", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# For non-blocking doors or if room1 didn't match, check blocking_room as fallback
|
||||
if not door_in_this_room and door_blocking_room and not door_blocking_room.is_empty():
|
||||
door_in_this_room = (door_blocking_room.x == room.x and door_blocking_room.y == room.y and \
|
||||
door_blocking_room.w == room.w and door_blocking_room.h == room.h)
|
||||
if door_in_this_room:
|
||||
print("RoomTrigger: Door ", child.name, " blocking_room matches this room (fallback check)")
|
||||
LogManager.log("RoomTrigger: Door " + str(child.name) + " blocking_room matches this room (fallback check)", LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("RoomTrigger: Door ", child.name, " blocking_room (", door_blocking_room.x if door_blocking_room else "none", ", ", door_blocking_room.y if door_blocking_room else "none", ") doesn't match this room (", room.x, ", ", room.y, ")")
|
||||
var blocking_room_x = str(door_blocking_room.x) if door_blocking_room and not door_blocking_room.is_empty() else "none"
|
||||
var blocking_room_y = str(door_blocking_room.y) if door_blocking_room and not door_blocking_room.is_empty() else "none"
|
||||
LogManager.log("RoomTrigger: Door " + str(child.name) + " blocking_room (" + blocking_room_x + ", " + blocking_room_y + ") doesn't match this room (" + str(room.x) + ", " + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
if not door_room1 and not door_blocking_room:
|
||||
print("RoomTrigger: Door ", child.name, " has no room1 or blocking_room set!")
|
||||
LogManager.log("RoomTrigger: Door " + str(child.name) + " has no room1 or blocking_room set!", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
if door_in_this_room:
|
||||
# This door is IN THIS room (blocks exits from this room) - add it to this trigger
|
||||
@@ -178,11 +184,15 @@ func _find_room_entities():
|
||||
var this_room_str = "(" + str(room.x) + ", " + str(room.y) + ")"
|
||||
|
||||
# Debug: Print door's room info to understand why it's matching multiple rooms
|
||||
print("RoomTrigger: WARNING - Door ", child.name, " already connected to trigger for room ", other_room_str, "!")
|
||||
print("RoomTrigger: Door room1: (", door_room1.x if door_room1 else "none", ", ", door_room1.y if door_room1 else "none", ")")
|
||||
print("RoomTrigger: Door blocking_room: (", door_blocking_room.x if door_blocking_room else "none", ", ", door_blocking_room.y if door_blocking_room else "none", ")")
|
||||
print("RoomTrigger: Current trigger room: ", this_room_str)
|
||||
print("RoomTrigger: Skipping this door - it belongs to the other trigger!")
|
||||
var door_room1_x_str = str(door_room1.x) if door_room1 and not door_room1.is_empty() else "none"
|
||||
var door_room1_y_str = str(door_room1.y) if door_room1 and not door_room1.is_empty() else "none"
|
||||
var door_blocking_room_x_str = str(door_blocking_room.x) if door_blocking_room and not door_blocking_room.is_empty() else "none"
|
||||
var door_blocking_room_y_str = str(door_blocking_room.y) if door_blocking_room and not door_blocking_room.is_empty() else "none"
|
||||
LogManager.log("RoomTrigger: WARNING - Door " + str(child.name) + " already connected to trigger for room " + other_room_str + "!", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Door room1: (" + door_room1_x_str + ", " + door_room1_y_str + ")", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Door blocking_room: (" + door_blocking_room_x_str + ", " + door_blocking_room_y_str + ")", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Current trigger room: " + this_room_str, LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Skipping this door - it belongs to the other trigger!", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Don't add to this trigger if already connected to another trigger
|
||||
if child in doors_in_room:
|
||||
@@ -193,7 +203,7 @@ func _find_room_entities():
|
||||
# CRITICAL: Only add if not already in the list (avoid duplicates)
|
||||
if not child in doors_in_room:
|
||||
doors_in_room.append(child)
|
||||
print("RoomTrigger: Added door ", child.name, " that is IN room (", room.x, ", ", room.y, ") to this trigger")
|
||||
LogManager.log("RoomTrigger: Added door " + str(child.name) + " that is IN room (" + str(room.x) + ", " + str(room.y) + ") to this trigger", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Set door's room trigger reference (should be null at this point, but set it anyway)
|
||||
if not child.room_trigger_area:
|
||||
@@ -202,13 +212,13 @@ func _find_room_entities():
|
||||
# This door is NOT in this room - ensure it's not in this trigger's list
|
||||
# This prevents doors from being accidentally connected to wrong triggers
|
||||
if child in doors_in_room:
|
||||
print("RoomTrigger: Removing door ", child.name, " from this trigger - it's not in this room (", room.x, ", ", room.y, ")")
|
||||
LogManager.log("RoomTrigger: Removing door " + str(child.name) + " from this trigger - it's not in this room (" + str(room.x) + ", " + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
doors_in_room.erase(child)
|
||||
if child.room_trigger_area == self:
|
||||
print("RoomTrigger: Disconnecting door ", child.name, " from this trigger - wrong room!")
|
||||
LogManager.log("RoomTrigger: Disconnecting door " + str(child.name) + " from this trigger - wrong room!", LogManager.CATEGORY_DUNGEON)
|
||||
child.room_trigger_area = null
|
||||
|
||||
print("RoomTrigger: Found ", doors_in_room.size(), " doors for room (", room.x, ", ", room.y, ")")
|
||||
LogManager.log("RoomTrigger: Found " + str(doors_in_room.size()) + " doors for room (" + str(room.x) + ", " + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Find enemies (only if room has enemies - skip for empty rooms to avoid unnecessary work)
|
||||
for child in entities_node.get_children():
|
||||
@@ -249,18 +259,18 @@ func _find_room_entities():
|
||||
switch_tile_y >= room_min_y and switch_tile_y < room_max_y:
|
||||
floor_switches_in_room.append(child)
|
||||
|
||||
print("RoomTrigger: Found ", enemies_in_room.size(), " enemies and ", floor_switches_in_room.size(), " switches for room (", room.x, ", ", room.y, ")")
|
||||
LogManager.log("RoomTrigger: Found " + str(enemies_in_room.size()) + " enemies and " + str(floor_switches_in_room.size()) + " switches for room (" + str(room.x) + ", " + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Update debug label after finding all entities (skip if label not created yet)
|
||||
if debug_label:
|
||||
call_deferred("_update_debug_label")
|
||||
|
||||
func _spawn_room_enemies():
|
||||
print("RoomTrigger: ===== _spawn_room_enemies() CALLED for room (", room.x, ", ", room.y, ") =====")
|
||||
LogManager.log("RoomTrigger: ===== _spawn_room_enemies() CALLED for room (" + str(room.x) + ", " + str(room.y) + ") =====", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Spawn enemies when player enters room (if room has spawners and not already spawned)
|
||||
if enemies_spawned:
|
||||
print("RoomTrigger: Already spawned enemies, skipping...")
|
||||
LogManager.log("RoomTrigger: Already spawned enemies, skipping...", LogManager.CATEGORY_DUNGEON)
|
||||
return # Already spawned enemies
|
||||
|
||||
# CRITICAL: Remove any existing smoke puffs before spawning new ones
|
||||
@@ -269,42 +279,42 @@ func _spawn_room_enemies():
|
||||
# Find enemy spawners for this room
|
||||
_find_room_spawners()
|
||||
|
||||
print("RoomTrigger: ===== Found ", enemy_spawners.size(), " spawners for room (", room.x, ", ", room.y, ") =====")
|
||||
LogManager.log("RoomTrigger: ===== Found " + str(enemy_spawners.size()) + " spawners for room (" + str(room.x) + ", " + str(room.y) + ") =====", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Spawn enemies from all spawners in this room (only once)
|
||||
if enemy_spawners.size() > 0:
|
||||
for spawner in enemy_spawners:
|
||||
if not is_instance_valid(spawner):
|
||||
print("RoomTrigger: WARNING - Invalid spawner found, skipping")
|
||||
LogManager.log("RoomTrigger: WARNING - Invalid spawner found, skipping", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
if not spawner.has_method("spawn_enemy"):
|
||||
print("RoomTrigger: WARNING - Spawner ", spawner.name, " doesn't have spawn_enemy method!")
|
||||
LogManager.log("RoomTrigger: WARNING - Spawner " + str(spawner.name) + " doesn't have spawn_enemy method!", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# CRITICAL: Verify spawner has enemy scenes set
|
||||
if "enemy_scenes" in spawner:
|
||||
if spawner.enemy_scenes.size() == 0:
|
||||
print("RoomTrigger: ERROR - Spawner ", spawner.name, " has empty enemy_scenes array! Cannot spawn!")
|
||||
LogManager.log_error("RoomTrigger: ERROR - Spawner " + str(spawner.name) + " has empty enemy_scenes array! Cannot spawn!", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
else:
|
||||
print("RoomTrigger: Spawner ", spawner.name, " has ", spawner.enemy_scenes.size(), " enemy scenes available")
|
||||
LogManager.log("RoomTrigger: Spawner " + str(spawner.name) + " has " + str(spawner.enemy_scenes.size()) + " enemy scenes available", LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("RoomTrigger: ERROR - Spawner ", spawner.name, " has no enemy_scenes property!")
|
||||
LogManager.log_error("RoomTrigger: ERROR - Spawner " + str(spawner.name) + " has no enemy_scenes property!", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
# CRITICAL: Verify spawner is on server (authority) - only server can spawn
|
||||
if multiplayer.has_multiplayer_peer() and not spawner.is_multiplayer_authority():
|
||||
print("RoomTrigger: WARNING - Spawner ", spawner.name, " is not multiplayer authority! Cannot spawn on client!")
|
||||
LogManager.log("RoomTrigger: WARNING - Spawner " + str(spawner.name) + " is not multiplayer authority! Cannot spawn on client!", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
print("RoomTrigger: Calling spawn_enemy() on spawner ", spawner.name, " at ", spawner.global_position)
|
||||
LogManager.log("RoomTrigger: Calling spawn_enemy() on spawner " + str(spawner.name) + " at " + str(spawner.global_position), LogManager.CATEGORY_DUNGEON)
|
||||
# Spawn enemies from this spawner (spawner will handle max_enemies check)
|
||||
# NOTE: spawn_enemy() is async (uses await), so we don't await it here - it will execute asynchronously
|
||||
spawner.spawn_enemy()
|
||||
|
||||
enemies_spawned = true
|
||||
print("RoomTrigger: Called spawn_enemy() on ", enemy_spawners.size(), " spawners in room at ", room.x, ", ", room.y)
|
||||
LogManager.log("RoomTrigger: Called spawn_enemy() on " + str(enemy_spawners.size()) + " spawners in room at " + str(room.x) + ", " + str(room.y), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Update debug label
|
||||
_update_debug_label()
|
||||
@@ -315,12 +325,15 @@ func _spawn_room_enemies():
|
||||
_find_room_entities() # Refresh enemy list after spawning completes
|
||||
_update_debug_label() # Update again after enemies spawn
|
||||
else:
|
||||
print("RoomTrigger: No spawners found for room (", room.x, ", ", room.y, ")")
|
||||
LogManager.log("RoomTrigger: No spawners found for room (" + str(room.x) + ", " + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
_update_debug_label()
|
||||
|
||||
func _cleanup_smoke_puffs():
|
||||
# Remove all existing smoke puffs in the scene before spawning new ones
|
||||
var entities_node = get_tree().get_first_node_in_group("game_world")
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var entities_node = null
|
||||
if game_world:
|
||||
entities_node = game_world.get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
entities_node = get_node_or_null("/root/GameWorld/Entities")
|
||||
|
||||
@@ -331,12 +344,12 @@ func _cleanup_smoke_puffs():
|
||||
var smoke_puffs_removed = 0
|
||||
for child in entities_node.get_children():
|
||||
if child.is_in_group("smoke_puff") or child.name.begins_with("SmokePuff"):
|
||||
print("RoomTrigger: Removing existing smoke puff: ", child.name)
|
||||
LogManager.log("RoomTrigger: Removing existing smoke puff: " + str(child.name), LogManager.CATEGORY_DUNGEON)
|
||||
child.queue_free()
|
||||
smoke_puffs_removed += 1
|
||||
|
||||
if smoke_puffs_removed > 0:
|
||||
print("RoomTrigger: Cleaned up ", smoke_puffs_removed, " existing smoke puffs before spawning")
|
||||
LogManager.log("RoomTrigger: Cleaned up " + str(smoke_puffs_removed) + " existing smoke puffs before spawning", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
func _find_room_spawners():
|
||||
# CRITICAL: Clear the list first to avoid accumulating old spawners
|
||||
@@ -345,26 +358,26 @@ func _find_room_spawners():
|
||||
# Find enemy spawners in this room
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world:
|
||||
print("RoomTrigger: ERROR - No game_world found when searching for spawners!")
|
||||
LogManager.log_error("RoomTrigger: ERROR - No game_world found when searching for spawners!", LogManager.CATEGORY_DUNGEON)
|
||||
return
|
||||
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if not entities_node:
|
||||
print("RoomTrigger: ERROR - No Entities node found when searching for spawners!")
|
||||
LogManager.log_error("RoomTrigger: ERROR - No Entities node found when searching for spawners!", LogManager.CATEGORY_DUNGEON)
|
||||
return
|
||||
|
||||
print("RoomTrigger: ===== Searching for spawners in room (", room.x, ", ", room.y, ", ", room.w, "x", room.h, ") =====")
|
||||
print("RoomTrigger: Entities node has ", entities_node.get_child_count(), " children")
|
||||
LogManager.log("RoomTrigger: ===== Searching for spawners in room (" + str(room.x) + ", " + str(room.y) + ", " + str(room.w) + "x" + str(room.h) + ") =====", LogManager.CATEGORY_DUNGEON)
|
||||
LogManager.log("RoomTrigger: Entities node has " + str(entities_node.get_child_count()) + " children", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Search for spawners (they might be direct children of Entities or in a Spawners node)
|
||||
var found_spawners_count = 0
|
||||
for child in entities_node.get_children():
|
||||
if child.name.begins_with("EnemySpawner_") or child.is_in_group("enemy_spawner"):
|
||||
found_spawners_count += 1
|
||||
print("RoomTrigger: Checking spawner: ", child.name, " at ", child.global_position)
|
||||
LogManager.log("RoomTrigger: Checking spawner: " + str(child.name) + " at " + str(child.global_position), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
if not is_instance_valid(child):
|
||||
print("RoomTrigger: Spawner is invalid, skipping")
|
||||
LogManager.log("RoomTrigger: Spawner is invalid, skipping", LogManager.CATEGORY_DUNGEON)
|
||||
continue
|
||||
|
||||
var spawner_in_room = false
|
||||
@@ -372,28 +385,32 @@ func _find_room_spawners():
|
||||
# First check if spawner has room metadata matching this room
|
||||
if child.has_meta("room"):
|
||||
var spawner_room = child.get_meta("room")
|
||||
print("RoomTrigger: Spawner has room metadata: (", spawner_room.x if spawner_room and not spawner_room.is_empty() else "none", ", ", spawner_room.y if spawner_room and not spawner_room.is_empty() else "none", ")")
|
||||
var spawner_room_x_str = str(spawner_room.x) if spawner_room and not spawner_room.is_empty() else "none"
|
||||
var spawner_room_y_str = str(spawner_room.y) if spawner_room and not spawner_room.is_empty() else "none"
|
||||
LogManager.log("RoomTrigger: Spawner has room metadata: (" + spawner_room_x_str + ", " + spawner_room_y_str + ")", LogManager.CATEGORY_DUNGEON)
|
||||
if spawner_room and not spawner_room.is_empty():
|
||||
# Compare rooms by position and size
|
||||
if spawner_room.x == room.x and spawner_room.y == room.y and \
|
||||
spawner_room.w == room.w and spawner_room.h == room.h:
|
||||
spawner_in_room = true
|
||||
print("RoomTrigger: ✓ Spawner room matches this trigger room!")
|
||||
LogManager.log("RoomTrigger: ✓ Spawner room matches this trigger room!", LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("RoomTrigger: ✗ Spawner room doesn't match - spawner: (", spawner_room.x, ",", spawner_room.y, ",", spawner_room.w, "x", spawner_room.h, "), trigger: (", room.x, ",", room.y, ",", room.w, "x", room.h, ")")
|
||||
LogManager.log("RoomTrigger: ✗ Spawner room doesn't match - spawner: (" + str(spawner_room.x) + "," + str(spawner_room.y) + "," + str(spawner_room.w) + "x" + str(spawner_room.h) + "), trigger: (" + str(room.x) + "," + str(room.y) + "," + str(room.w) + "x" + str(room.h) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Also check blocking_room metadata (fallback)
|
||||
if not spawner_in_room and child.has_meta("blocking_room"):
|
||||
var blocking_room = child.get_meta("blocking_room")
|
||||
print("RoomTrigger: Spawner has blocking_room metadata: (", blocking_room.x if blocking_room and not blocking_room.is_empty() else "none", ", ", blocking_room.y if blocking_room and not blocking_room.is_empty() else "none", ")")
|
||||
var blocking_room_x_str = str(blocking_room.x) if blocking_room and not blocking_room.is_empty() else "none"
|
||||
var blocking_room_y_str = str(blocking_room.y) if blocking_room and not blocking_room.is_empty() else "none"
|
||||
LogManager.log("RoomTrigger: Spawner has blocking_room metadata: (" + blocking_room_x_str + ", " + blocking_room_y_str + ")", LogManager.CATEGORY_DUNGEON)
|
||||
if blocking_room and not blocking_room.is_empty():
|
||||
# Compare rooms by position and size
|
||||
if blocking_room.x == room.x and blocking_room.y == room.y and \
|
||||
blocking_room.w == room.w and blocking_room.h == room.h:
|
||||
spawner_in_room = true
|
||||
print("RoomTrigger: ✓ Spawner blocking_room matches this trigger room!")
|
||||
LogManager.log("RoomTrigger: ✓ Spawner blocking_room matches this trigger room!", LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("RoomTrigger: ✗ Spawner blocking_room doesn't match - spawner: (", blocking_room.x, ",", blocking_room.y, ",", blocking_room.w, "x", blocking_room.h, "), trigger: (", room.x, ",", room.y, ",", room.w, "x", room.h, ")")
|
||||
LogManager.log("RoomTrigger: ✗ Spawner blocking_room doesn't match - spawner: (" + str(blocking_room.x) + "," + str(blocking_room.y) + "," + str(blocking_room.w) + "x" + str(blocking_room.h) + "), trigger: (" + str(room.x) + "," + str(room.y) + "," + str(room.w) + "x" + str(room.h) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Also check by position (fallback if no room metadata)
|
||||
if not spawner_in_room:
|
||||
@@ -405,22 +422,22 @@ func _find_room_spawners():
|
||||
var room_min_y = room.y + 2
|
||||
var room_max_y = room.y + room.h - 2
|
||||
|
||||
print("RoomTrigger: Checking by position: spawner at tile (", spawner_tile_x, ", ", spawner_tile_y, "), room bounds: (", room_min_x, "-", room_max_x, ", ", room_min_y, "-", room_max_y, ")")
|
||||
LogManager.log("RoomTrigger: Checking by position: spawner at tile (" + str(spawner_tile_x) + ", " + str(spawner_tile_y) + "), room bounds: (" + str(room_min_x) + "-" + str(room_max_x) + ", " + str(room_min_y) + "-" + str(room_max_y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
if spawner_tile_x >= room_min_x and spawner_tile_x < room_max_x and \
|
||||
spawner_tile_y >= room_min_y and spawner_tile_y < room_max_y:
|
||||
spawner_in_room = true
|
||||
print("RoomTrigger: Spawner is within room bounds!")
|
||||
LogManager.log("RoomTrigger: Spawner is within room bounds!", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
if spawner_in_room and not child in enemy_spawners:
|
||||
enemy_spawners.append(child)
|
||||
print("RoomTrigger: ✓ Added spawner ", child.name, " to this room trigger")
|
||||
LogManager.log("RoomTrigger: ✓ Added spawner " + str(child.name) + " to this room trigger", LogManager.CATEGORY_DUNGEON)
|
||||
elif spawner_in_room:
|
||||
print("RoomTrigger: Spawner already in list, skipping")
|
||||
LogManager.log("RoomTrigger: Spawner already in list, skipping", LogManager.CATEGORY_DUNGEON)
|
||||
else:
|
||||
print("RoomTrigger: Spawner is NOT in this room, skipping")
|
||||
LogManager.log("RoomTrigger: Spawner is NOT in this room, skipping", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
print("RoomTrigger: Total spawners found: ", found_spawners_count, ", spawners in this room: ", enemy_spawners.size())
|
||||
LogManager.log("RoomTrigger: Total spawners found: " + str(found_spawners_count) + ", spawners in this room: " + str(enemy_spawners.size()), LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Update debug label after finding entities
|
||||
call_deferred("_update_debug_label")
|
||||
@@ -430,13 +447,13 @@ func _create_debug_label():
|
||||
# Only create if debug mode is enabled
|
||||
var network_manager = get_node_or_null("/root/NetworkManager")
|
||||
if not network_manager:
|
||||
print("RoomTrigger: NetworkManager not found, skipping debug label")
|
||||
LogManager.log("RoomTrigger: NetworkManager not found, skipping debug label", LogManager.CATEGORY_DUNGEON)
|
||||
return
|
||||
if not network_manager.show_room_labels:
|
||||
print("RoomTrigger: Debug mode not enabled (show_room_labels = false), skipping debug label for room (", room.x, ", ", room.y, ")")
|
||||
LogManager.log("RoomTrigger: Debug mode not enabled (show_room_labels = false), skipping debug label for room (" + str(room.x) + ", " + str(room.y) + ")", LogManager.CATEGORY_DUNGEON)
|
||||
return
|
||||
|
||||
print("RoomTrigger: Creating debug label for room (", room.x, ", ", room.y, ") - debug mode enabled")
|
||||
LogManager.log("RoomTrigger: Creating debug label for room (" + str(room.x) + ", " + str(room.y) + ") - debug mode enabled", LogManager.CATEGORY_DUNGEON)
|
||||
|
||||
# Skip if label already exists
|
||||
if debug_label and is_instance_valid(debug_label):
|
||||
|
||||
@@ -123,17 +123,30 @@ func _on_body_entered(body):
|
||||
|
||||
# Hit successful - play impact sound and deal damage
|
||||
$SfxImpact.play()
|
||||
var enemy_peer_id = body.get_multiplayer_authority()
|
||||
if enemy_peer_id != 0:
|
||||
# If enemy is on the same peer (server), call directly
|
||||
if multiplayer.is_server() and enemy_peer_id == multiplayer.get_unique_id():
|
||||
body.rpc_take_damage(damage, attacker_pos, is_crit)
|
||||
|
||||
# Use game_world to route damage request instead of direct RPC to avoid node path issues
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var enemy_name = body.name
|
||||
var enemy_index = body.get_meta("enemy_index") if body.has_meta("enemy_index") else -1
|
||||
|
||||
if game_world and game_world.has_method("_request_enemy_damage"):
|
||||
if multiplayer.is_server():
|
||||
# Server can call directly
|
||||
game_world._request_enemy_damage(enemy_name, enemy_index, damage, attacker_pos, is_crit)
|
||||
else:
|
||||
# Send RPC to enemy's authority (server) - clients can do this!
|
||||
body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, is_crit)
|
||||
# Client sends RPC to server
|
||||
game_world._request_enemy_damage.rpc_id(1, enemy_name, enemy_index, damage, attacker_pos, is_crit)
|
||||
else:
|
||||
# Fallback: broadcast if we can't get peer_id
|
||||
body.rpc_take_damage.rpc(damage, attacker_pos, is_crit)
|
||||
# Fallback: try direct call (may fail if node path doesn't match)
|
||||
var enemy_peer_id = body.get_multiplayer_authority()
|
||||
if enemy_peer_id != 0:
|
||||
if multiplayer.is_server() and enemy_peer_id == multiplayer.get_unique_id():
|
||||
body.rpc_take_damage(damage, attacker_pos, is_crit)
|
||||
else:
|
||||
body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, is_crit)
|
||||
else:
|
||||
body.rpc_take_damage.rpc(damage, attacker_pos, is_crit)
|
||||
|
||||
# Debug print - handle null player_owner safely
|
||||
var owner_name: String = "none"
|
||||
var is_authority: bool = false
|
||||
@@ -149,16 +162,46 @@ func _on_body_entered(body):
|
||||
# Boxes have health property
|
||||
body.health -= damage
|
||||
if body.health <= 0:
|
||||
# Break locally first
|
||||
# Get object identifier - prefer consistent name (InteractableObject_X) or use object_index meta
|
||||
var obj_name = body.name
|
||||
var obj_index = -1
|
||||
|
||||
# Check if object has object_index meta (set when spawned in dungeon)
|
||||
if body.has_meta("object_index"):
|
||||
obj_index = body.get_meta("object_index")
|
||||
# Use consistent naming if we have the index
|
||||
if obj_index >= 0:
|
||||
obj_name = "InteractableObject_%d" % obj_index
|
||||
|
||||
# Fallback: if name doesn't start with "InteractableObject_", try to find consistent name
|
||||
if not obj_name.begins_with("InteractableObject_") and obj_index < 0:
|
||||
# Try to find object by position or other means
|
||||
# For now, just use the name as-is and log a warning
|
||||
print("Sword projectile: Warning - object ", body.name, " doesn't have consistent naming!")
|
||||
|
||||
# Sync break to server (which will broadcast to all clients)
|
||||
# Use game_world to route the RPC to avoid node path resolution issues
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
# Only send RPC if game_world exists and is valid
|
||||
if game_world and is_instance_valid(game_world) and game_world.is_inside_tree() and game_world.has_method("_sync_object_break"):
|
||||
if multiplayer.is_server():
|
||||
# Server: broadcast to ready clients (breaks locally too via _sync_break)
|
||||
if game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_object_break", [obj_name])
|
||||
print("Sword projectile synced box break to all clients: ", obj_name)
|
||||
else:
|
||||
# Client: send request to server (server will break locally and broadcast to all)
|
||||
# Also send object_index if available for fallback lookup
|
||||
game_world._sync_object_break.rpc_id(1, obj_name)
|
||||
print("Sword projectile requested box break on server: ", obj_name, " (index: ", obj_index, ")")
|
||||
else:
|
||||
print("Sword projectile: GameWorld not ready, skipping box break sync for ", obj_name)
|
||||
|
||||
# Break locally AFTER syncing (so node still exists for RPC path resolution)
|
||||
if body.has_method("_break_into_pieces"):
|
||||
body._break_into_pieces()
|
||||
print("Sword projectile broke box locally: ", body.name)
|
||||
|
||||
# Sync break to OTHER clients via RPC
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
if body.has_method("_sync_break"):
|
||||
body._sync_break.rpc()
|
||||
print("Sword projectile synced box break to other clients")
|
||||
print("Sword projectile hit object: ", body.name)
|
||||
|
||||
# Push the hit target away slightly (only for non-enemies)
|
||||
|
||||
373
src/webrtc/LICENSE.libdatachannel
Normal file
373
src/webrtc/LICENSE.libdatachannel
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
373
src/webrtc/LICENSE.libjuice
Normal file
373
src/webrtc/LICENSE.libjuice
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
35
src/webrtc/LICENSE.libsrtp
Normal file
35
src/webrtc/LICENSE.libsrtp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2001-2017 Cisco Systems, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* Neither the name of the Cisco Systems, Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
553
src/webrtc/LICENSE.mbedtls
Normal file
553
src/webrtc/LICENSE.mbedtls
Normal file
@@ -0,0 +1,553 @@
|
||||
Mbed TLS files are provided under a dual [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
|
||||
OR [GPL-2.0-or-later](https://spdx.org/licenses/GPL-2.0-or-later.html) license.
|
||||
This means that users may choose which of these licenses they take the code
|
||||
under.
|
||||
|
||||
The full text of each of these licenses is given below.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
===============================================================================
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
21
src/webrtc/LICENSE.plog
Normal file
21
src/webrtc/LICENSE.plog
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Sergey Podobry
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
27
src/webrtc/LICENSE.usrsctp
Normal file
27
src/webrtc/LICENSE.usrsctp
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2015, Randall Stewart and Michael Tuexen
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of usrsctp nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
21
src/webrtc/LICENSE.webrtc-native
Normal file
21
src/webrtc/LICENSE.webrtc-native
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Godot Engine
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
BIN
src/webrtc/lib/libwebrtc_native.android.template_debug.arm64.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.android.template_debug.arm64.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.android.template_debug.x86_64.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.android.template_debug.x86_64.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.ios.template_debug.arm64.dylib
Normal file
BIN
src/webrtc/lib/libwebrtc_native.ios.template_debug.arm64.dylib
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.ios.template_release.arm64.dylib
Normal file
BIN
src/webrtc/lib/libwebrtc_native.ios.template_release.arm64.dylib
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.arm32.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.arm32.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.arm64.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.arm64.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.x86_32.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.x86_32.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.x86_64.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_debug.x86_64.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.arm32.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.arm32.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.arm64.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.arm64.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.x86_32.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.x86_32.so
Normal file
Binary file not shown.
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.x86_64.so
Normal file
BIN
src/webrtc/lib/libwebrtc_native.linux.template_release.x86_64.so
Normal file
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libwebrtc_native.macos.template_debug.universal.dylib</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.godotengine.webrtc-native</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>libwebrtc_native.macos.template_debug.universal</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>webrtc_native</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libwebrtc_native.macos.template_release.universal.dylib</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.godotengine.webrtc-native</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>libwebrtc_native.macos.template_release.universal</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>webrtc_native</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
30
src/webrtc/webrtc.gdextension
Normal file
30
src/webrtc/webrtc.gdextension
Normal file
@@ -0,0 +1,30 @@
|
||||
[configuration]
|
||||
|
||||
entry_symbol = "webrtc_extension_init"
|
||||
compatibility_minimum = 4.1
|
||||
|
||||
[libraries]
|
||||
|
||||
linux.debug.x86_64 = "lib/libwebrtc_native.linux.template_debug.x86_64.so"
|
||||
linux.debug.x86_32 = "lib/libwebrtc_native.linux.template_debug.x86_32.so"
|
||||
linux.debug.arm64 = "lib/libwebrtc_native.linux.template_debug.arm64.so"
|
||||
linux.debug.arm32 = "lib/libwebrtc_native.linux.template_debug.arm32.so"
|
||||
macos.debug = "lib/libwebrtc_native.macos.template_debug.universal.framework"
|
||||
windows.debug.x86_64 = "lib/libwebrtc_native.windows.template_debug.x86_64.dll"
|
||||
windows.debug.x86_32 = "lib/libwebrtc_native.windows.template_debug.x86_32.dll"
|
||||
android.debug.arm64 = "lib/libwebrtc_native.android.template_debug.arm64.so"
|
||||
android.debug.x86_64 = "lib/libwebrtc_native.android.template_debug.x86_64.so"
|
||||
ios.debug.arm64 = "lib/libwebrtc_native.ios.template_debug.arm64.dylib"
|
||||
ios.debug.x86_64 = "lib/libwebrtc_native.ios.template_debug.x86_64.simulator.dylib"
|
||||
|
||||
linux.release.x86_64 = "lib/libwebrtc_native.linux.template_release.x86_64.so"
|
||||
linux.release.x86_32 = "lib/libwebrtc_native.linux.template_release.x86_32.so"
|
||||
linux.release.arm64 = "lib/libwebrtc_native.linux.template_release.arm64.so"
|
||||
linux.release.arm32 = "lib/libwebrtc_native.linux.template_release.arm32.so"
|
||||
macos.release = "lib/libwebrtc_native.macos.template_release.universal.framework"
|
||||
windows.release.x86_64 = "lib/libwebrtc_native.windows.template_release.x86_64.dll"
|
||||
windows.release.x86_32 = "lib/libwebrtc_native.windows.template_release.x86_32.dll"
|
||||
android.release.arm64 = "lib/libwebrtc_native.android.template_release.arm64.so"
|
||||
android.release.x86_64 = "lib/libwebrtc_native.android.template_release.x86_64.so"
|
||||
ios.release.arm64 = "lib/libwebrtc_native.ios.template_release.arm64.dylib"
|
||||
ios.release.x86_64 = "lib/libwebrtc_native.ios.template_release.x86_64.simulator.dylib"
|
||||
1
src/webrtc/webrtc.gdextension.uid
Normal file
1
src/webrtc/webrtc.gdextension.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://o5h08t2r11hh
|
||||
Reference in New Issue
Block a user