Move the real player authority to server
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

- Add rust bindings for the server player controller
- Implement reconciliation loop for checking the player state
- Place spawners on the test map
- Add Containerfile and helm chart
This commit is contained in:
2025-01-29 12:25:26 +01:00
parent 3e6eab08fd
commit d958a5b275
94 changed files with 3002 additions and 144 deletions

View File

@ -0,0 +1,115 @@
extends Node3D
class_name PlayerPlaceholder
@export var owner_id: int = 0
@export var initial_position: Vector3 = Vector3(0, 0, 0)
# -- Components
@onready var client_node: CharacterBody3D = $PlayerControlledNode
@onready var server_node: PlayerServerNode = $ServerControlledNode
@export var character_speed: int = 5
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
set_multiplayer_authority(1)
client_node.set_multiplayer_authority(owner_id)
$PlayerControlledNode/CameraMount.set_multiplayer_authority(owner_id)
switch_players_camera.rpc_id(owner_id)
server_node.global_position = initial_position
client_node.global_position = server_node.global_position
client_node.rotation.y = server_node.rotation.y
client_node.rotation.x = server_node.rotation.x
if multiplayer.is_server():
$Timer.start()
if owner_id != multiplayer.get_unique_id():
client_node.queue_free()
else:
pass
#client_node._add_legs_to_first_view()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
#if desired_player_state.globa_position != real_player_state.globa_position:
#desired_player_state.globa_position = real_player_state.globa_position
pass
func _physics_process(delta: float) -> void:
pass
@rpc("authority", "call_local", "reliable")
func switch_players_camera():
$PlayerControlledNode/CameraMount/Camera3D.make_current()
func set_server_position(position: Vector3):
initial_position = position
func _on_Timer_timeout():
$Timer.start()
func _on_timer_timeout() -> void:
if multiplayer.is_server():
verify_position.rpc_id(owner_id)
verify_rotation.rpc_id(owner_id)
$Timer.start()
pass # Replace with function body.
@rpc("authority", "call_local")
func verify_position():
var desired_position: Vector3 = client_node.global_position
send_position.rpc_id(1, desired_position.x, desired_position.y, desired_position.z)
@rpc("any_peer", "call_local")
func send_position(x: float, y: float, z: float):
var desired_position: Vector3 = Vector3(x, y, z)
if multiplayer.is_server():
var real_position: Vector3 = server_node.global_position
var difference: Vector3 = desired_position - real_position
if is_vector_a_lower_than_b(difference, Vector3(0.3, 0.3, 0.3)):
server_node.global_position = desired_position
else:
var new_position: Vector3 = desired_position.lerp(real_position, 0.5)
adjust_position.rpc_id(owner_id, new_position.x, new_position.y, new_position.z)
@rpc("authority", "call_local")
func adjust_position(x: float, y: float, z: float):
var desired_position: Vector3 = Vector3(x, y, z)
if owner_id == multiplayer.get_unique_id():
push_warning("player position is not valid, adjusting")
client_node.global_position = desired_position
@rpc("authority", "call_local")
func verify_rotation():
var desired_rotation: Vector3 = client_node.rotation
send_rotation.rpc_id(1, desired_rotation.x, desired_rotation.y, desired_rotation.z)
@rpc("any_peer", "call_local")
func send_rotation(x: float, y: float, z: float):
var desired_rotation: Vector3 = Vector3(x, y, z)
if multiplayer.is_server():
var real_rotation: Vector3 = server_node.rotation
var difference: Vector3 = desired_rotation - real_rotation
if is_vector_a_lower_than_b(difference, Vector3(0.3, 0.3, 0.3)):
server_node.rotation = desired_rotation
else:
var new_rotation: Vector3 = desired_rotation.lerp(real_rotation, 0.5)
adjust_rotation.rpc_id(owner_id, new_rotation.x, new_rotation.y, new_rotation.z)
@rpc("any_peer", "call_local")
func adjust_rotation(x: float, y: float, z: float):
var desired_rotation: Vector3 = Vector3(x, y, z)
if owner_id == multiplayer.get_unique_id():
push_warning("player rotation is not valid, adjusting")
client_node.rotation = desired_rotation
func is_vector_a_lower_than_b(vec_a: Vector3, vec_b: Vector3) -> bool:
return vec_a.x < vec_b.x and vec_a.y < vec_b.y and vec_a.z < vec_b.z

View File

@ -0,0 +1,224 @@
[gd_scene load_steps=15 format=3 uid="uid://bmm3brgvr4d86"]
[ext_resource type="Script" path="res://scenes/player/player_input_controller.gd" id="1_m6tul"]
[ext_resource type="Script" path="res://scenes/player/placeholder.gd" id="1_ts455"]
[ext_resource type="Texture2D" uid="uid://oopj5mj1vdp0" path="res://assets/crosshairs/crosshair_default.png" id="3_8ulsx"]
[ext_resource type="Script" path="res://scenes/player/server_player_controller.gd" id="3_f1bhn"]
[ext_resource type="PackedScene" uid="uid://dtvo21mk1webd" path="res://scenes/weapon/guns/ak/with_hands.tscn" id="3_xxv4y"]
[ext_resource type="PackedScene" uid="uid://1txob6jskn5s" path="res://scenes/characters/blue/dummy.tscn" id="6_e3cnh"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_taqso"]
radius = 0.4
height = 1.6
[sub_resource type="LabelSettings" id="LabelSettings_3bk8i"]
font_size = 70
[sub_resource type="LabelSettings" id="LabelSettings_adbcb"]
font_size = 100
font_color = Color(0.756874, 0, 0.223924, 1)
[sub_resource type="Animation" id="Animation_falg4"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.125),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_mkk0p"]
resource_name = "move"
length = 0.5
loop_mode = 2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.125, 0.25, 0.375, 0.5),
"transitions": PackedFloat32Array(1, 2, 1, 2, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(-0.1, 0.1, -0.1), Vector3(0, 0, 0), Vector3(-0.1, 0.1, -0.1), Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_ah507"]
resource_name = "shooting"
length = 0.2
loop_mode = 1
step = 0.1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2),
"transitions": PackedFloat32Array(1, 0.233258, 8.57419),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, -0.01, -0.01), Vector3(0, 0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_u1b6p"]
_data = {
"RESET": SubResource("Animation_falg4"),
"move": SubResource("Animation_mkk0p"),
"shooting": SubResource("Animation_ah507")
}
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_7vlrn"]
properties/0/path = NodePath("ServerControlledNode:position")
properties/0/spawn = true
properties/0/replication_mode = 1
properties/1/path = NodePath(".:owner_id")
properties/1/spawn = true
properties/1/replication_mode = 2
properties/2/path = NodePath("ServerControlledNode:rotation")
properties/2/spawn = true
properties/2/replication_mode = 1
properties/3/path = NodePath(".:initial_position")
properties/3/spawn = true
properties/3/replication_mode = 2
[node name="Placeholder" type="Node3D"]
script = ExtResource("1_ts455")
[node name="PlayerControlledNode" type="CharacterBody3D" parent="."]
collision_layer = 2
script = ExtResource("1_m6tul")
[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerControlledNode"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.786919, 0)
shape = SubResource("CapsuleShape3D_taqso")
[node name="CameraMount" type="Node3D" parent="PlayerControlledNode"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.29568, -0.412934)
[node name="Camera3D" type="Camera3D" parent="PlayerControlledNode/CameraMount"]
cull_mask = 524287
[node name="HUD" type="Control" parent="PlayerControlledNode/CameraMount/Camera3D"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_right = 8.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="TextureRect" type="TextureRect" parent="PlayerControlledNode/CameraMount/Camera3D/HUD"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -36.0
offset_top = -36.0
offset_right = 36.0
offset_bottom = 36.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("3_8ulsx")
[node name="HealthIndicator" type="Label" parent="PlayerControlledNode/CameraMount/Camera3D/HUD"]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -23.0
offset_right = 40.0
grow_vertical = 0
text = "100"
label_settings = SubResource("LabelSettings_3bk8i")
[node name="FPS" type="Label" parent="PlayerControlledNode/CameraMount/Camera3D/HUD" groups=["player_placeholder"]]
layout_mode = 1
offset_right = 40.0
offset_bottom = 23.0
text = "0"
label_settings = SubResource("LabelSettings_adbcb")
[node name="SubViewportContainer" type="SubViewportContainer" parent="PlayerControlledNode/CameraMount/Camera3D/HUD"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
stretch = true
[node name="SubViewport" type="SubViewport" parent="PlayerControlledNode/CameraMount/Camera3D/HUD/SubViewportContainer"]
transparent_bg = true
handle_input_locally = false
msaa_2d = 1
msaa_3d = 1
canvas_cull_mask = 4294443008
size = Vector2i(1920, 1964)
render_target_update_mode = 4
[node name="Camera3D" type="Camera3D" parent="PlayerControlledNode/CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport"]
cull_mask = 524288
fov = 40.0
[node name="GunMount" type="Node3D" parent="PlayerControlledNode/CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport/Camera3D"]
[node name="AnimationPlayer" type="AnimationPlayer" parent="PlayerControlledNode/CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport/Camera3D/GunMount"]
active = false
libraries = {
"": SubResource("AnimationLibrary_u1b6p")
}
[node name="WithHands" parent="PlayerControlledNode/CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport/Camera3D/GunMount" instance=ExtResource("3_xxv4y")]
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0.51335, -0.655058, -1.67097)
visible = false
[node name="BulletStartingPoint" type="Node3D" parent="PlayerControlledNode/CameraMount"]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, -0.42185)
[node name="AimRay" type="RayCast3D" parent="PlayerControlledNode/CameraMount/BulletStartingPoint"]
target_position = Vector3(0, 0, 1000)
hit_from_inside = true
collide_with_areas = true
[node name="Node3D" type="Node3D" parent="PlayerControlledNode"]
[node name="ServerControlledNodeBak" type="CharacterBody3D" parent="."]
collision_layer = 4
script = ExtResource("3_f1bhn")
[node name="Dummy" parent="ServerControlledNodeBak" instance=ExtResource("6_e3cnh")]
visible = false
[node name="CollisionShape3D" type="CollisionShape3D" parent="ServerControlledNodeBak"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.57384, 0)
shape = SubResource("CapsuleShape3D_taqso")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_7vlrn")
[node name="Timer" type="Timer" parent="."]
wait_time = 0.1
[node name="ServerControlledNode" type="PlayerServerNode" parent="."]
collision_layer = 4
[node name="CollisionShape3D" type="CollisionShape3D" parent="ServerControlledNode"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.786919, 0)
shape = SubResource("CapsuleShape3D_taqso")
[node name="CSGBox3D" type="CSGBox3D" parent="ServerControlledNode"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.47058, 0)
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]

View File

@ -0,0 +1,146 @@
# ---------------------------------------------------------------------
# ---------------------------------------------------------------------
# This script is supposed to control the node that is rendered on the
# client side, and send the changes to the server node
# ---------------------------------------------------------------------
extends CharacterBody3D
@export var jumping := false
@export var input_direction := Vector2()
@onready var camera_mount = $CameraMount
@onready var camera = $CameraMount/Camera3D
@onready var placeholder: Node3D = $'..'
var paused := false
const JUMP_VELOCITY = 4.5
#func _ready() -> void:
var current_gun: String = "ak"
@onready var gun_mount: Node3D = $CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport/Camera3D/GunMount
@onready var gun_mount_anim: AnimationPlayer = $CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport/Camera3D/GunMount/AnimationPlayer
var gun_with_hands: Node3D = null
@onready var bullet_starting_point: Node3D = $CameraMount/BulletStartingPoint
@onready var aim_ray: RayCast3D = $CameraMount/BulletStartingPoint/AimRay
@onready var gun_camera: Camera3D = $CameraMount/Camera3D/HUD/SubViewportContainer/SubViewport/Camera3D
var current_weapon_damage: int
var current_weapon_bullet_speed: int
var current_weapon_cooldown_interwal: float
var look_dir: Vector2
func _ready() -> void:
global_position = $"..".initial_position
# -- TODO: It should not be hardcoded
# Define a format string with placeholder '%s'
var path_tmpl := "res://scenes/weapon/guns/%s/with_hands.tscn"
var path := path_tmpl % current_gun
var scene: PackedScene = ResourceLoader.load(path)
var node: Node3D = scene.instantiate()
node.scale = Vector3(0.5,0.5,0.5)
node.position = Vector3(0.5, -0.5, -1.5)
current_weapon_bullet_speed = node.bullet_speed
current_weapon_cooldown_interwal = node.cooldown
current_weapon_damage = node.damage
gun_with_hands = node
gun_mount.add_child(node)
func _input(event):
if multiplayer.get_unique_id() == get_multiplayer_authority():
if Input.is_action_just_pressed("jump"): jumping = true
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
look_dir = event.relative * 1
rotation.y -= look_dir.x * camera_sens * 1.0
camera_mount.rotation.x = clamp(camera_mount.rotation.x - look_dir.y * camera_sens * 1.0, -1.5, 1.5)
server_node.set_rotation_y.rpc_id(1, rotation.y)
server_node.set_rotation_x.rpc_id(1, rotation.x)
#server_node.set_input_direction.rpc_id(1, rotation.x, rotation.y)
@onready var server_node: PlayerServerNode = $"../ServerControlledNode"
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta: float) -> void:
if multiplayer.get_unique_id() == get_multiplayer_authority():
if !paused:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
input_direction = Input.get_vector("move_left", "move_right", "move_forward", "move_backwards")
server_node.set_input_direction.rpc_id(1, input_direction)
if Input.is_action_just_pressed("jump"):
server_node.jump.rpc_id(1)
if Input.is_action_pressed("shot"): _shoot()
if multiplayer.get_unique_id() == get_multiplayer_authority():
if not is_on_floor():
velocity += get_gravity() * delta
if is_on_floor() && jumping:
velocity.y = JUMP_VELOCITY
jumping = false
var direction := (transform.basis * Vector3(input_direction.x, 0, input_direction.y)).normalized()
if is_on_floor():
if direction:
#first_view_legs_anim.play("Run Forward")
gun_mount_anim.play("move")
velocity.x = direction.x * placeholder.character_speed
velocity.z = direction.z * placeholder.character_speed
else:
velocity.x = move_toward(velocity.x, 0, placeholder.character_speed)
velocity.z = move_toward(velocity.z, 0, placeholder.character_speed)
func _process(delta: float) -> void:
move_and_slide()
var cant_shoot: bool = false
func _shoot():
if not cant_shoot:
gun_with_hands.shoot()
cant_shoot = true
await get_tree().create_timer(current_weapon_cooldown_interwal).timeout
cant_shoot = false
#func _physics_process(delta: float) -> void:
#if multiplayer.get_unique_id() == get_multiplayer_authority():
#if not is_on_floor():
#velocity += get_gravity() * delta
#if is_on_floor() && jumping:
#velocity.y = JUMP_VELOCITY
#jumping = false
#var direction := (transform.basis * Vector3(input_direction.x, 0, input_direction.y)).normalized()
#if is_on_floor():
#if direction:
#velocity.x = direction.x * placeholder.character_speed
#velocity.z = direction.z * placeholder.character_speed
#else:
#velocity.x = move_toward(velocity.x, 0, placeholder.character_speed)
#velocity.z = move_toward(velocity.z, 0, placeholder.character_speed)
#move_and_slide()
var first_view_legs_anim: AnimationPlayer = null
func _add_legs_to_first_view() -> Error :
# -- TODO: It should not be hardcoded
var path := "res://scenes/characters/blue/dummy.tscn"
if not ResourceLoader.exists(path):
return ERR_DOES_NOT_EXIST
var scene: PackedScene = ResourceLoader.load(path)
if not scene.can_instantiate():
return ERR_CANT_OPEN
var node: Node3D = scene.instantiate()
var skeleton: Skeleton3D = node.find_child("Skeleton3D")
var bone := skeleton.find_bone("mixamorig_Spine")
if bone != -1:
skeleton.set_bone_pose_scale(bone, Vector3(0, 0, 0))
add_child(node)
var animation_node: AnimationPlayer = node.find_child("AnimationPlayer")
if animation_node != null:
first_view_legs_anim = animation_node
return OK
var camera_sens: float = 0.002
@rpc("authority", "reliable")
func set_current_rotation(rotation: Vector3):
rotation = rotation
@rpc("any_peer", "reliable")
func set_current_position(x: float, y: float, z: float):
global_position = Vector3(x, y ,z)
@rpc("any_peer", "call_local", "reliable")
func jump():
jumping = true

View File

@ -0,0 +1,48 @@
class_name ServerControlledPlayer
extends CharacterBody3D
const JUMP_VELOCITY = 4.5
func _ready() -> void:
pass
@export_category("ServerControlledPlayer")
@onready var placeholder: Node3D = $'..'
var input_direction := Vector2()
var input_rotation_y
@rpc("call_local", "any_peer", "unreliable_ordered")
func set_input_direction(new_input_direction: Vector2):
input_direction = new_input_direction
@rpc("call_local", "any_peer", "unreliable_ordered")
func set_rotation_y(new_rotation_y: float):
rotation.y = new_rotation_y
@rpc("call_local", "any_peer", "unreliable_ordered")
func set_rotation_x(new_rotation_x: float):
rotation.x = new_rotation_x
@rpc("call_local", "any_peer", "unreliable_ordered")
func jump():
jumping = true
var jumping := false
func _process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
if is_on_floor() && jumping:
velocity.y = JUMP_VELOCITY
jumping = false
if is_on_floor():
var direction := (transform.basis * Vector3(input_direction.x, 0, input_direction.y)).normalized()
#if is_on_floor():
if direction:
velocity.x = direction.x * placeholder.character_speed
velocity.z = direction.z * placeholder.character_speed
else:
velocity.x = move_toward(velocity.x, 0, placeholder.character_speed)
velocity.z = move_toward(velocity.z, 0, placeholder.character_speed)
func _physics_process(delta: float) -> void:
move_and_slide()