diff --git a/scenes/player.tscn b/scenes/player.tscn index 68faebe..af9bfa6 100644 --- a/scenes/player.tscn +++ b/scenes/player.tscn @@ -11,6 +11,8 @@ radius = 0.35 height = 1.6 [node name="Player" type="CharacterBody3D"] +collision_layer = 2 +collision_mask = 1 script = ExtResource("1_player") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] diff --git a/scenes/server_player.tscn b/scenes/server_player.tscn index cbd6858..2136920 100644 --- a/scenes/server_player.tscn +++ b/scenes/server_player.tscn @@ -7,6 +7,8 @@ radius = 0.35 height = 1.6 [node name="Player" type="CharacterBody3D"] +collision_layer = 2 +collision_mask = 1 script = ExtResource("1") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] diff --git a/scripts/player.gd b/scripts/player.gd index cefcb2e..eb78528 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -26,6 +26,7 @@ const MOUSE_SENSITIVITY: float = 0.003 var _input_direction := Vector2.ZERO var _camera_pitch: float = -0.12 +var _spawn_position: Vector3 = Vector3(0, 1.5, 0) ## Vertical offset applied to the assembled avatar so its feet (origin y=0) ## line up with the bottom of the player's collision capsule. @@ -69,6 +70,7 @@ func _ready() -> void: else: push_warning("player.gd: no PlayerSpawn marker found; spawning at origin") global_position = Vector3(0, 1.0, 0) + _spawn_position = global_position # The spring arm must not collide with the player's own capsule (they're # on the same layer), or the camera gets yanked right up against the @@ -192,6 +194,11 @@ func _physics_process(delta: float) -> void: move_and_slide() + # Safety net -- never fall forever if something goes wrong. + if global_position.y < -10.0: + global_position = _spawn_position + velocity = Vector3.ZERO + net_input = { "move_x": direction.x, "move_z": direction.z, "jump": jumped, "look_yaw": rotation.y, diff --git a/scripts/player_spawner.gd b/scripts/player_spawner.gd index 104b6ff..f574cd9 100644 --- a/scripts/player_spawner.gd +++ b/scripts/player_spawner.gd @@ -14,6 +14,10 @@ const GRAVITY := 9.8 var peer_id: int = -1 +## Where the server spawned this player (set by server.gd). Used as the +## respawn point if the player somehow ends up below the world. +var spawn_position: Vector3 = Vector3(0, 1.5, 0) + ## Highest input seq applied authoritatively so far (echoed in snapshots so ## the client can drop acked inputs and replay the rest). var last_applied_seq: int = 0 @@ -38,6 +42,13 @@ func _physics_process(delta: float) -> void: rotation.y = _look_yaw + # Safety net: if a player ends up below the world (physics shove, tunnel + # through the floor), put them back on their spawn instead of falling + # forever. + if global_position.y < -10.0: + global_position = spawn_position + velocity = Vector3.ZERO + func _integrate(move_x: float, move_z: float, jump: bool, delta: float) -> void: if not is_on_floor(): diff --git a/scripts/server.gd b/scripts/server.gd index 5850b91..2061ce1 100644 --- a/scripts/server.gd +++ b/scripts/server.gd @@ -30,6 +30,9 @@ var status_file_path: String = "" var players: Dictionary = {} var tick: int = 0 var is_running: bool = false +## Monotonic count of joins ever -- drives spawn spread so two players never +## land on the same spot (which used to shove one through the floor). +var _join_count: int = 0 var _player_scene: PackedScene = preload("res://scenes/server_player.tscn") var _level_scene: PackedScene = preload("res://scenes/worlds/test_level.tscn") @@ -65,6 +68,16 @@ func _ready() -> void: print("[Server] world=%s listening on ENet %d (%d Hz)" % [world_id, port, PHYSICS_HZ]) +## A spawn point offset from the level's marker so joins don't stack. Golden- +## angle spiral: evenly spread, never exactly repeats, grows slowly outward. +func _spawn_for(index: int) -> Vector3: + if index == 0: + return _spawn_point + var angle := index * 2.399963 # golden angle (radians) + var radius := 0.9 + 0.25 * float(index) + return _spawn_point + Vector3(cos(angle) * radius, 0.0, sin(angle) * radius) + + func _parse_args() -> void: var args := OS.get_cmdline_user_args() var i := 0 @@ -127,7 +140,10 @@ func _on_peer_connected(peer_id: int) -> void: player.name = "Player_%d" % peer_id player.peer_id = peer_id add_child(player) - player.global_position = _spawn_point + var spawn := _spawn_for(_join_count) + _join_count += 1 + player.global_position = spawn + player.spawn_position = spawn players[peer_id] = player print("[Server] peer %d joined (%d players)" % [peer_id, players.size()]) _write_status()