Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcdd75afc0 | ||
|
|
cd034d1aea |
@@ -28,12 +28,13 @@ func _build_geometry() -> void:
|
|||||||
fill.light_energy = 0.3
|
fill.light_energy = 0.3
|
||||||
|
|
||||||
_build_dust2_layout()
|
_build_dust2_layout()
|
||||||
_build_player()
|
|
||||||
|
|
||||||
# Override player spawn point for Dust 2
|
func _spawn_player(pid: int) -> CharacterBody3D:
|
||||||
if _player:
|
var player = super._spawn_player(pid)
|
||||||
_player.global_position = Vector3(0, 1, 50)
|
player.position = Vector3(randf_range(-2, 2), 1, 50 + randf_range(-2, 2))
|
||||||
_player.look_at(Vector3(0, 1, 0), Vector3.UP)
|
# Looking at (0,1,0) from (0,1,50) is facing straight down the -Z axis, which is rotation = 0,0,0
|
||||||
|
player.rotation = Vector3.ZERO
|
||||||
|
return player
|
||||||
|
|
||||||
func _build_dust2_layout() -> void:
|
func _build_dust2_layout() -> void:
|
||||||
# Materials
|
# Materials
|
||||||
|
|||||||
+69
-22
@@ -25,6 +25,31 @@ func _ready() -> void:
|
|||||||
_build_geometry()
|
_build_geometry()
|
||||||
_build_hud()
|
_build_hud()
|
||||||
|
|
||||||
|
# Multiplayer Spawning
|
||||||
|
var spawner = MultiplayerSpawner.new()
|
||||||
|
spawner.name = "PlayerSpawner"
|
||||||
|
spawner.spawn_path = "." # Spawn directly as children of the level
|
||||||
|
spawner.spawn_function = _spawn_player
|
||||||
|
add_child(spawner)
|
||||||
|
|
||||||
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
|
if nm and nm.multiplayer.is_server():
|
||||||
|
# Spawn existing players
|
||||||
|
for pid in nm.player_stats.keys():
|
||||||
|
spawner.spawn(pid)
|
||||||
|
|
||||||
|
# Spawn late joiners
|
||||||
|
nm.player_connected.connect(func(pid):
|
||||||
|
spawner.spawn(pid)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Despawn on disconnect
|
||||||
|
nm.player_disconnected.connect(func(pid):
|
||||||
|
var p = get_node_or_null(str(pid))
|
||||||
|
if p:
|
||||||
|
p.queue_free()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _build_materials() -> void:
|
func _build_materials() -> void:
|
||||||
pass
|
pass
|
||||||
@@ -40,7 +65,6 @@ func _build_geometry() -> void:
|
|||||||
_build_speed_corridor()
|
_build_speed_corridor()
|
||||||
_build_target_dummy()
|
_build_target_dummy()
|
||||||
_build_lighting()
|
_build_lighting()
|
||||||
_build_player()
|
|
||||||
|
|
||||||
|
|
||||||
# ── Utility ───────────────────────────────────────────────────────────────────
|
# ── Utility ───────────────────────────────────────────────────────────────────
|
||||||
@@ -261,12 +285,24 @@ func _build_lighting() -> void:
|
|||||||
|
|
||||||
# ── Player ────────────────────────────────────────────────────────────────────
|
# ── Player ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func _build_player() -> void:
|
func _spawn_player(pid: int) -> CharacterBody3D:
|
||||||
var player := CharacterBody3D.new()
|
var player := CharacterBody3D.new()
|
||||||
player.name = "Player"
|
player.name = str(pid)
|
||||||
player.position = Vector3(0, 1.0, 0)
|
player.set_multiplayer_authority(pid)
|
||||||
add_child(player)
|
|
||||||
_player = player
|
# Use a slightly random spawn position to avoid exact overlapping
|
||||||
|
player.position = Vector3(randf_range(-2, 2), 2.0, randf_range(-2, 2))
|
||||||
|
|
||||||
|
# Network Synchronizer
|
||||||
|
var sync = MultiplayerSynchronizer.new()
|
||||||
|
sync.name = "MultiplayerSynchronizer"
|
||||||
|
sync.set_multiplayer_authority(pid)
|
||||||
|
var rep_config = SceneReplicationConfig.new()
|
||||||
|
rep_config.add_property(":position")
|
||||||
|
rep_config.add_property(":rotation")
|
||||||
|
rep_config.add_property("HeadPivot:rotation")
|
||||||
|
sync.replication_config = rep_config
|
||||||
|
player.add_child(sync)
|
||||||
|
|
||||||
# Collision shape
|
# Collision shape
|
||||||
var col_shape := CollisionShape3D.new()
|
var col_shape := CollisionShape3D.new()
|
||||||
@@ -282,11 +318,11 @@ func _build_player() -> void:
|
|||||||
if mover_script:
|
if mover_script:
|
||||||
player.set_script(mover_script)
|
player.set_script(mover_script)
|
||||||
|
|
||||||
# Humanoid Model for Player (Shadows only so it doesn't clip with camera)
|
# Humanoid Model for Player (Shadows only locally, visible to others)
|
||||||
var humanoid = load("res://characters/humanoid_model.gd").new()
|
var humanoid = load("res://characters/humanoid_model.gd").new()
|
||||||
humanoid.name = "HumanoidModel"
|
humanoid.name = "HumanoidModel"
|
||||||
humanoid.color = Color(0.2, 0.4, 0.8) # Blueish for player
|
humanoid.color = Color(0.2, 0.4, 0.8) # Blueish for player
|
||||||
humanoid.shadows_only = true
|
humanoid.shadows_only = (pid == multiplayer.get_unique_id())
|
||||||
humanoid.position = Vector3(0, -0.9, 0) # Offset from center to feet
|
humanoid.position = Vector3(0, -0.9, 0) # Offset from center to feet
|
||||||
player.add_child(humanoid)
|
player.add_child(humanoid)
|
||||||
|
|
||||||
@@ -324,7 +360,7 @@ func _build_player() -> void:
|
|||||||
|
|
||||||
var camera := Camera3D.new()
|
var camera := Camera3D.new()
|
||||||
camera.name = "Camera3D"
|
camera.name = "Camera3D"
|
||||||
camera.current = true
|
camera.current = (pid == multiplayer.get_unique_id())
|
||||||
camera.fov = 90.0
|
camera.fov = 90.0
|
||||||
head_pivot.add_child(camera)
|
head_pivot.add_child(camera)
|
||||||
|
|
||||||
@@ -344,27 +380,38 @@ func _build_player() -> void:
|
|||||||
# Store original capsule height
|
# Store original capsule height
|
||||||
sm.original_capsule_height = capsule.height
|
sm.original_capsule_height = capsule.height
|
||||||
|
|
||||||
# ── Bootstrap: set_script() doesn't trigger _ready() ─────────────────
|
# ── Dependencies ────────────────────────────────────────────────────────
|
||||||
# Set references BEFORE calling _ready() since sm._ready() will switch_to("ground")
|
# Set references before the node enters the tree. Godot will call _ready()
|
||||||
# which needs machine.player to already be set.
|
# automatically when MultiplayerSpawner adds the node to the scene.
|
||||||
sm.player = player
|
sm.player = player
|
||||||
sm.params = player.params
|
sm.params = player.params
|
||||||
head_pivot.params = player.params
|
head_pivot.params = player.params
|
||||||
|
|
||||||
# Initialize in dependency order: machine (enters initial state) → player → camera
|
# Authority configuration
|
||||||
if sm.has_method("_ready"):
|
var is_local = (pid == multiplayer.get_unique_id())
|
||||||
sm._ready()
|
if is_local:
|
||||||
if player.has_method("_ready"):
|
_player = player
|
||||||
player._ready()
|
|
||||||
if head_pivot.has_method("_ready"):
|
|
||||||
head_pivot._ready()
|
|
||||||
|
|
||||||
# Ensure processing is enabled
|
|
||||||
player.set_physics_process(true)
|
player.set_physics_process(true)
|
||||||
player.set_process(true)
|
player.set_process(true)
|
||||||
sm.set_physics_process(true)
|
sm.set_physics_process(true)
|
||||||
|
else:
|
||||||
|
# Disable processing/input for remote players
|
||||||
|
player.set_physics_process(false)
|
||||||
|
player.set_process(false)
|
||||||
|
player.set_process_input(false)
|
||||||
|
sm.set_physics_process(false)
|
||||||
|
sm.set_process(false)
|
||||||
|
sm.set_process_input(false)
|
||||||
|
head_pivot.set_process_input(false)
|
||||||
|
head_pivot.set_process(false)
|
||||||
|
head_pivot.set_physics_process(false)
|
||||||
|
var wman = camera.get_node_or_null("WeaponManager")
|
||||||
|
if wman:
|
||||||
|
wman.set_process_input(false)
|
||||||
|
wman.set_process(false)
|
||||||
|
wman.set_physics_process(false)
|
||||||
|
|
||||||
_player = player
|
return player
|
||||||
|
|
||||||
|
|
||||||
# ── HUD ───────────────────────────────────────────────────────────────────────
|
# ── HUD ───────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ func _ensure_machine() -> MovementStateMachine:
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
func _physics_process(_delta: float) -> void:
|
func _physics_process(_delta: float) -> void:
|
||||||
|
if not is_multiplayer_authority(): return
|
||||||
var speed = velocity.length()
|
var speed = velocity.length()
|
||||||
var wind_factor = clampf((speed - 10.0) / 25.0, 0.0, 1.0)
|
var wind_factor = clampf((speed - 10.0) / 25.0, 0.0, 1.0)
|
||||||
# Decibel scale is non-linear, so lerping from -80 means it stays inaudible for a long time.
|
# Decibel scale is non-linear, so lerping from -80 means it stays inaudible for a long time.
|
||||||
@@ -401,6 +402,7 @@ func _on_movement_event(ev: String, data: Dictionary) -> void:
|
|||||||
grapple_latch_player.play()
|
grapple_latch_player.play()
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
if not is_multiplayer_authority(): return
|
||||||
if is_dead:
|
if is_dead:
|
||||||
# Continuously follow the ragdoll torso
|
# Continuously follow the ragdoll torso
|
||||||
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
||||||
@@ -632,6 +634,7 @@ func die(impulse: Vector3 = Vector3.ZERO) -> void:
|
|||||||
set_process_input(true)
|
set_process_input(true)
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
|
if not is_multiplayer_authority(): return
|
||||||
if is_dead and death_screen and death_screen.visible:
|
if is_dead and death_screen and death_screen.visible:
|
||||||
if event is InputEventKey or event is InputEventMouseButton or event is InputEventJoypadButton:
|
if event is InputEventKey or event is InputEventMouseButton or event is InputEventJoypadButton:
|
||||||
if event.is_pressed() and not event.is_echo():
|
if event.is_pressed() and not event.is_echo():
|
||||||
|
|||||||
+31
-19
@@ -10,6 +10,10 @@ var _host_lobby_players_list: ItemList
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||||
|
|
||||||
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
|
if nm:
|
||||||
|
nm.connection_succeeded.connect(_on_connection_succeeded)
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# BACKGROUND: 3D DIORAMA (Sliding Player)
|
# BACKGROUND: 3D DIORAMA (Sliding Player)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -231,6 +235,7 @@ func _ready() -> void:
|
|||||||
var _gamemode_opt = OptionButton.new()
|
var _gamemode_opt = OptionButton.new()
|
||||||
_gamemode_opt.add_theme_font_size_override("font_size", 24)
|
_gamemode_opt.add_theme_font_size_override("font_size", 24)
|
||||||
_gamemode_opt.add_item("Deathmatch", 0)
|
_gamemode_opt.add_item("Deathmatch", 0)
|
||||||
|
_gamemode_opt.add_to_group("host_only_ui")
|
||||||
_gamemode_opt.item_selected.connect(func(index: int):
|
_gamemode_opt.item_selected.connect(func(index: int):
|
||||||
if get_node_or_null("/root/NetworkManager"):
|
if get_node_or_null("/root/NetworkManager"):
|
||||||
get_node("/root/NetworkManager").current_gamemode = _gamemode_opt.get_item_text(index)
|
get_node("/root/NetworkManager").current_gamemode = _gamemode_opt.get_item_text(index)
|
||||||
@@ -313,6 +318,7 @@ func _build_level_card(parent: Container, title: String, scene_path: String, gra
|
|||||||
overlay.add_child(lbl)
|
overlay.add_child(lbl)
|
||||||
|
|
||||||
if is_multiplayer:
|
if is_multiplayer:
|
||||||
|
btn.add_to_group("host_only_ui")
|
||||||
btn.pressed.connect(func(): get_node("/root/NetworkManager").rpc_load_level.rpc(scene_path))
|
btn.pressed.connect(func(): get_node("/root/NetworkManager").rpc_load_level.rpc(scene_path))
|
||||||
else:
|
else:
|
||||||
btn.pressed.connect(func():
|
btn.pressed.connect(func():
|
||||||
@@ -350,16 +356,9 @@ func _on_multiplayer_back_pressed() -> void:
|
|||||||
_ui_vbox.show()
|
_ui_vbox.show()
|
||||||
|
|
||||||
func _on_host_pressed() -> void:
|
func _on_host_pressed() -> void:
|
||||||
_multiplayer_panel.hide()
|
|
||||||
_host_lobby_panel.show()
|
|
||||||
var nm = get_node("/root/NetworkManager")
|
var nm = get_node("/root/NetworkManager")
|
||||||
nm.host_game()
|
if nm.host_game() == OK:
|
||||||
_update_lobby_players()
|
_on_connection_succeeded()
|
||||||
if not nm.player_connected.is_connected(_on_network_player_connected):
|
|
||||||
nm.player_connected.connect(_on_network_player_connected)
|
|
||||||
nm.player_disconnected.connect(_on_network_player_disconnected)
|
|
||||||
if not nm.stats_updated.is_connected(_update_lobby_players):
|
|
||||||
nm.stats_updated.connect(_update_lobby_players)
|
|
||||||
|
|
||||||
func _on_direct_connect_pressed() -> void:
|
func _on_direct_connect_pressed() -> void:
|
||||||
_multiplayer_panel.hide()
|
_multiplayer_panel.hide()
|
||||||
@@ -373,7 +372,25 @@ func _on_connect_confirm_pressed() -> void:
|
|||||||
var ip = _direct_connect_panel.get_node("IPInput").text
|
var ip = _direct_connect_panel.get_node("IPInput").text
|
||||||
var port = _direct_connect_panel.get_node("PortInput").text.to_int()
|
var port = _direct_connect_panel.get_node("PortInput").text.to_int()
|
||||||
get_node("/root/NetworkManager").join_game(ip, port)
|
get_node("/root/NetworkManager").join_game(ip, port)
|
||||||
# For simplicity, we can show a connecting label or just wait for the game to start via RPC
|
|
||||||
|
func _on_connection_succeeded() -> void:
|
||||||
|
_direct_connect_panel.hide()
|
||||||
|
_multiplayer_panel.hide()
|
||||||
|
_host_lobby_panel.show()
|
||||||
|
|
||||||
|
var nm = get_node("/root/NetworkManager")
|
||||||
|
var is_host = nm.multiplayer.is_server()
|
||||||
|
for node in get_tree().get_nodes_in_group("host_only_ui"):
|
||||||
|
if node is BaseButton:
|
||||||
|
node.disabled = not is_host
|
||||||
|
|
||||||
|
if not nm.player_connected.is_connected(_on_network_player_connected):
|
||||||
|
nm.player_connected.connect(_on_network_player_connected)
|
||||||
|
nm.player_disconnected.connect(_on_network_player_disconnected)
|
||||||
|
if not nm.stats_updated.is_connected(_update_lobby_players):
|
||||||
|
nm.stats_updated.connect(_update_lobby_players)
|
||||||
|
|
||||||
|
_update_lobby_players()
|
||||||
|
|
||||||
func _on_lobby_back_pressed() -> void:
|
func _on_lobby_back_pressed() -> void:
|
||||||
_host_lobby_panel.hide()
|
_host_lobby_panel.hide()
|
||||||
@@ -396,19 +413,14 @@ func _update_lobby_players() -> void:
|
|||||||
if not _host_lobby_players_list: return
|
if not _host_lobby_players_list: return
|
||||||
_host_lobby_players_list.clear()
|
_host_lobby_players_list.clear()
|
||||||
var nm = get_node("/root/NetworkManager")
|
var nm = get_node("/root/NetworkManager")
|
||||||
var host_name = SettingsManager.username
|
|
||||||
var host_ping = 0
|
|
||||||
if nm.player_stats.has(1): host_ping = nm.player_stats[1].get("ping", 0)
|
|
||||||
|
|
||||||
for pid in nm.connected_players:
|
for pid in nm.player_stats.keys():
|
||||||
var p_ping = 0
|
var p_name = nm.player_stats[pid].get("username", "Unknown")
|
||||||
if nm.player_stats.has(pid): p_ping = nm.player_stats[pid].get("ping", 0)
|
var p_ping = nm.player_stats[pid].get("ping", 0)
|
||||||
|
|
||||||
if pid == 1:
|
if pid == 1:
|
||||||
_host_lobby_players_list.add_item("%s (Host) - %sms" % [host_name, host_ping])
|
_host_lobby_players_list.add_item("%s (Host) - %sms" % [p_name, p_ping])
|
||||||
else:
|
else:
|
||||||
var p_name = "Player " + str(pid)
|
|
||||||
if nm.player_stats.has(pid): p_name = nm.player_stats[pid].get("username", p_name)
|
|
||||||
_host_lobby_players_list.add_item("%s - %sms" % [p_name, p_ping])
|
_host_lobby_players_list.add_item("%s - %sms" % [p_name, p_ping])
|
||||||
|
|
||||||
func _on_loadouts_pressed() -> void:
|
func _on_loadouts_pressed() -> void:
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ func _ready() -> void:
|
|||||||
_nm.stats_updated.connect(_update_hud)
|
_nm.stats_updated.connect(_update_hud)
|
||||||
_nm.killfeed_event.connect(_on_killfeed_event)
|
_nm.killfeed_event.connect(_on_killfeed_event)
|
||||||
_nm.match_state_updated.connect(_update_timer)
|
_nm.match_state_updated.connect(_update_timer)
|
||||||
|
_update_hud()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if Input.is_action_just_pressed("scoreboard"):
|
if Input.is_action_just_pressed("scoreboard"):
|
||||||
|
|||||||
+15
-12
@@ -93,17 +93,16 @@ func _resume() -> void:
|
|||||||
func _respawn() -> void:
|
func _respawn() -> void:
|
||||||
_resume() # Unpause
|
_resume() # Unpause
|
||||||
|
|
||||||
# Find local player
|
# Find local player using their unique multiplayer ID
|
||||||
var local_player: Node3D = null
|
var local_player: Node3D = null
|
||||||
|
var pid = multiplayer.get_unique_id()
|
||||||
|
|
||||||
if get_tree().current_scene.has_node("Players"):
|
if get_tree().current_scene:
|
||||||
for p in get_tree().current_scene.get_node("Players").get_children():
|
local_player = get_tree().current_scene.get_node_or_null(str(pid))
|
||||||
if p.has_method("is_multiplayer_authority") and p.is_multiplayer_authority():
|
|
||||||
local_player = p
|
|
||||||
break
|
|
||||||
|
|
||||||
if not local_player:
|
if not local_player:
|
||||||
local_player = get_tree().current_scene.get_node_or_null("Player")
|
var spawner = get_tree().current_scene.get_node_or_null("PlayerSpawner")
|
||||||
|
if spawner:
|
||||||
|
local_player = spawner.get_node_or_null(str(pid))
|
||||||
|
|
||||||
if local_player and local_player.has_method("take_damage"):
|
if local_player and local_player.has_method("take_damage"):
|
||||||
local_player.take_damage(9999, Vector3.ZERO)
|
local_player.take_damage(9999, Vector3.ZERO)
|
||||||
@@ -271,10 +270,12 @@ func _build_ui() -> void:
|
|||||||
user_save.pressed.connect(func():
|
user_save.pressed.connect(func():
|
||||||
SettingsManager.username = user_input.text
|
SettingsManager.username = user_input.text
|
||||||
SettingsManager._save_settings()
|
SettingsManager._save_settings()
|
||||||
if NetworkManager.multiplayer.has_multiplayer_peer() and not NetworkManager.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
NetworkManager.update_username.rpc_id(1, user_input.text)
|
if nm:
|
||||||
|
if nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
|
nm.update_username.rpc_id(1, user_input.text)
|
||||||
else:
|
else:
|
||||||
NetworkManager.update_username(user_input.text)
|
nm.update_username(user_input.text)
|
||||||
)
|
)
|
||||||
user_hbox.add_child(user_save)
|
user_hbox.add_child(user_save)
|
||||||
|
|
||||||
@@ -684,7 +685,9 @@ func _show_return_dialog() -> void:
|
|||||||
|
|
||||||
func _return_to_main_menu() -> void:
|
func _return_to_main_menu() -> void:
|
||||||
_resume() # Reset pause state and cursor mode
|
_resume() # Reset pause state and cursor mode
|
||||||
NetworkManager.disconnect_game()
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
|
if nm:
|
||||||
|
nm.disconnect_game()
|
||||||
get_tree().change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
get_tree().change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
||||||
|
|
||||||
func _show_main_menu() -> void:
|
func _show_main_menu() -> void:
|
||||||
|
|||||||
Reference in New Issue
Block a user