2025-01-25 08:03:06 +00:00
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# This script is supposed to handle character movement logic
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
class_name PlayerInput extends CharacterBody3D
|
|
|
|
|
2025-01-26 13:39:11 +00:00
|
|
|
|
|
|
|
@export var health: int = 100
|
2025-01-26 09:59:59 +00:00
|
|
|
|
2025-01-25 08:03:06 +00:00
|
|
|
@export_category("PlayerInput")
|
|
|
|
|
|
|
|
const SPEED = 5.0
|
|
|
|
const JUMP_VELOCITY = 4.5
|
|
|
|
|
|
|
|
var mouse_captured: bool = false
|
|
|
|
|
|
|
|
@onready var player_synchronizer: MultiplayerSynchronizer = $PlayerSynchronizer
|
|
|
|
@onready var first_view_camera_mount: Node3D = $FirstPersonCameraMount
|
|
|
|
@onready var first_view_camera: Camera3D = $FirstPersonCameraMount/Camera
|
|
|
|
@onready var model_mount: Node3D = $ModelMount
|
2025-01-26 13:39:11 +00:00
|
|
|
var model: Node3D = null
|
2025-01-25 08:03:06 +00:00
|
|
|
|
|
|
|
# -- This node is supposed to be spawned per player, and since each
|
|
|
|
# -- player has an id, it is used for giving a node a name. So we can
|
|
|
|
# -- use it here to tell a controlled node from the rest models
|
|
|
|
@onready var owner_id: int = str($".".name).to_int()
|
|
|
|
var multiplayer_id: int = 0
|
|
|
|
|
|
|
|
# -- Character state
|
|
|
|
var alive: bool = true
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
player_synchronizer.set_multiplayer_authority(owner_id)
|
|
|
|
multiplayer_id = multiplayer.get_unique_id()
|
|
|
|
# -- Separate logic for player and other models that are controlled
|
|
|
|
# -- by other players on the server
|
|
|
|
# -- TODO: If player is alive, it must not be able to see other
|
|
|
|
# -- cameras, but if player is dead, it must be able to
|
|
|
|
# -- switch between other players
|
|
|
|
if _is_current_player():
|
|
|
|
var err := _add_first_view_model()
|
|
|
|
if err != OK:
|
|
|
|
print("Error occured: " + str(err))
|
2025-01-26 13:39:11 +00:00
|
|
|
err = _add_legs_to_first_view()
|
2025-01-25 08:03:06 +00:00
|
|
|
if err != OK:
|
|
|
|
print("Error occured: " + str(err))
|
2025-01-26 13:39:11 +00:00
|
|
|
var world_model := _add_world_model()
|
|
|
|
if world_model == null:
|
|
|
|
print("Error occured: " + "couldn't load the world model")
|
|
|
|
|
|
|
|
_enable_camera()
|
|
|
|
else:
|
|
|
|
var world_model := _add_world_model()
|
|
|
|
if world_model == null:
|
|
|
|
print("Error occured: " + "couldn't load the world model")
|
|
|
|
_hide_camera_mount()
|
2025-01-25 08:03:06 +00:00
|
|
|
|
|
|
|
_capture_mouse()
|
|
|
|
|
|
|
|
# -- Add a world model to the player, that should be seen by other players
|
|
|
|
# -- on the server
|
2025-01-26 13:39:11 +00:00
|
|
|
func _add_world_model() -> Node3D :
|
2025-01-25 08:03:06 +00:00
|
|
|
# -- TODO: It should not be hardcoded
|
|
|
|
var path := "res://scenes/characters/blue/dummy.tscn"
|
|
|
|
if not ResourceLoader.exists(path):
|
2025-01-26 13:39:11 +00:00
|
|
|
push_error(ERR_DOES_NOT_EXIST)
|
|
|
|
return null
|
2025-01-25 08:03:06 +00:00
|
|
|
var scene: PackedScene = ResourceLoader.load(path)
|
|
|
|
if not scene.can_instantiate():
|
2025-01-26 13:39:11 +00:00
|
|
|
push_error(ERR_CANT_OPEN)
|
|
|
|
return null
|
2025-01-25 08:03:06 +00:00
|
|
|
var node: Node3D = scene.instantiate()
|
2025-01-26 13:39:11 +00:00
|
|
|
model = node
|
2025-01-25 08:03:06 +00:00
|
|
|
model_mount.add_child(node)
|
2025-01-26 13:39:11 +00:00
|
|
|
if _is_current_player():
|
|
|
|
node.make_invisible()
|
|
|
|
first_view_camera.cull_mask &= ~(1 << 1)
|
|
|
|
return node
|
|
|
|
|
|
|
|
func _hide_camera_mount():
|
|
|
|
first_view_camera_mount.visible = 0
|
|
|
|
hud.visible = 0
|
|
|
|
|
|
|
|
|
|
|
|
func make_node_invisible_for_camera(node: Node3D, camera: Camera3D):
|
|
|
|
if node and camera:
|
|
|
|
# Set the node to Layer 2 (or any other layer you want)
|
|
|
|
node.visibility_layer = 1 << 1 # Assign the node to Layer 2
|
|
|
|
# Disable Layer 2 on the camera's culling mask (this makes it invisible to this camera)
|
|
|
|
camera.cull_mask &= ~(1 << 1) # Disable Layer 2 on this camera
|
2025-01-25 08:03:06 +00:00
|
|
|
|
2025-01-25 16:24:57 +00:00
|
|
|
var current_gun: String = "ak"
|
|
|
|
@onready var gun_mount: Node3D = $FirstPersonCameraMount/GunMount
|
|
|
|
@onready var gun_mount_anim: AnimationPlayer = $FirstPersonCameraMount/GunMount/AnimationPlayer
|
|
|
|
var gun_with_hands: Node3D = null
|
2025-01-26 13:39:11 +00:00
|
|
|
|
2025-01-25 08:03:06 +00:00
|
|
|
# -- Add the first person view to pthe player
|
|
|
|
func _add_first_view_model() -> Error :
|
2025-01-25 16:24:57 +00:00
|
|
|
# -- 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
|
2025-01-25 08:03:06 +00:00
|
|
|
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()
|
2025-01-25 16:24:57 +00:00
|
|
|
node.scale = Vector3(0.03,0.03,0.03)
|
|
|
|
node.position = Vector3(0.02, -0.03, -0.07)
|
2025-01-28 09:51:45 +00:00
|
|
|
current_weapon_bullet_speed = node.bullet_speed
|
|
|
|
current_weapon_cooldown_interwal = node.cooldown
|
|
|
|
current_weapon_damage = node.damage
|
2025-01-25 16:24:57 +00:00
|
|
|
gun_with_hands = node
|
|
|
|
gun_mount.add_child(node)
|
2025-01-25 08:03:06 +00:00
|
|
|
return OK
|
2025-01-26 13:39:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
var first_view_legs_anim: AnimationPlayer = null
|
|
|
|
# -- Add a world model to the player, that should be seen by other players
|
|
|
|
# -- on the server
|
|
|
|
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))
|
|
|
|
model_mount.add_child(node)
|
|
|
|
var animation_node: AnimationPlayer = node.find_child("AnimationPlayer")
|
|
|
|
if animation_node != null:
|
|
|
|
first_view_legs_anim = animation_node
|
|
|
|
return OK
|
|
|
|
|
2025-01-25 08:03:06 +00:00
|
|
|
|
|
|
|
func _enable_camera():
|
|
|
|
first_view_camera.make_current()
|
|
|
|
|
|
|
|
func _capture_mouse() -> void:
|
|
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
|
|
mouse_captured = true
|
|
|
|
|
|
|
|
func _release_mouse() -> void:
|
|
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
|
|
mouse_captured = false
|
|
|
|
|
|
|
|
# -- Does the peer own the node
|
|
|
|
func _is_current_player() -> bool:
|
|
|
|
if owner_id == multiplayer_id:
|
|
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# -- Input controller
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
var move_dir: Vector2 # Input direction for movement
|
|
|
|
var look_dir: Vector2 # Input direction for look/aim
|
|
|
|
|
|
|
|
var walk_vel: Vector3 # Walking velocity
|
|
|
|
var grav_vel: Vector3 # Gravity velocity
|
|
|
|
var jump_vel: Vector3 # Jumping velocity
|
|
|
|
|
|
|
|
@export_range(0.1, 3.0, 0.1) var jump_height: float = 1 # m
|
|
|
|
@export_range(0.1, 3.0, 0.1, "or_greater") var camera_sens: float = 1
|
|
|
|
|
2025-01-25 16:24:57 +00:00
|
|
|
|
2025-01-25 08:03:06 +00:00
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
|
|
if _is_current_player():
|
|
|
|
if event is InputEventMouseMotion:
|
|
|
|
look_dir = event.relative * 0.001
|
|
|
|
if mouse_captured: _rotate_camera()
|
|
|
|
#if Input.is_action_just_pressed("jump"): jumping = true
|
|
|
|
if Input.is_action_just_pressed("exit"): get_tree().quit()
|
|
|
|
if Input.is_action_pressed("shot"): _shoot()
|
|
|
|
#if str($"..".name).to_int() == multiplayer.get_unique_id():
|
|
|
|
#if Input.is_action_just_pressed("shot"): $UpperTorso/ViewModelCamera.shot()
|
|
|
|
#if Input.is_action_just_pressed("reload"): $Body/UpperTorso/CameraMount/Camera.reload()
|
|
|
|
|
|
|
|
func _rotate_camera(sens_mod: float = 1.0) -> void:
|
|
|
|
#if str($"..".name).to_int() == multiplayer.get_unique_id():
|
|
|
|
rotation.y -= look_dir.x * camera_sens * sens_mod
|
|
|
|
first_view_camera_mount.rotation.x = clamp(first_view_camera_mount.rotation.x - look_dir.y * camera_sens * sens_mod, -1.5, 1.5)
|
|
|
|
|
2025-01-26 13:39:11 +00:00
|
|
|
@onready var hud = $FirstPersonCameraMount/HUD
|
|
|
|
@onready var health_indicator = $FirstPersonCameraMount/HUD/HealthIndicator
|
|
|
|
@onready var fps_indicator = $FirstPersonCameraMount/HUD/FPS
|
|
|
|
func _process(delta: float) -> void:
|
|
|
|
health_indicator.text = str(health)
|
|
|
|
fps_indicator.text = str(Engine.get_frames_per_second())
|
|
|
|
if health == 0:
|
|
|
|
alive = false
|
|
|
|
var world: Node3D = find_parent("ElTest")
|
|
|
|
var spawner: MultiplayerSpawner = world.find_child("ObjectsSpawner")
|
|
|
|
var objects_root: Node3D = world.find_child("Objects")
|
|
|
|
spawner.spawn(model)
|
|
|
|
model.reparent(get_tree().get_root())
|
|
|
|
model.die()
|
|
|
|
queue_free()
|
|
|
|
|
|
|
|
|
2025-01-25 08:03:06 +00:00
|
|
|
func _physics_process(delta: float) -> void:
|
|
|
|
# Add the gravity.
|
|
|
|
if not is_on_floor():
|
|
|
|
velocity += get_gravity() * delta
|
|
|
|
|
|
|
|
# Handle jump.
|
|
|
|
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
|
|
velocity.y = JUMP_VELOCITY
|
|
|
|
|
|
|
|
# Get the input direction and handle the movement/deceleration.
|
|
|
|
# As good practice, you should replace UI actions with custom gameplay actions.
|
|
|
|
#var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
|
|
|
# -- It shouldn't be possible to change direction during the jumps
|
|
|
|
if is_on_floor():
|
|
|
|
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_backwards")
|
|
|
|
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
|
|
|
if direction:
|
2025-01-25 16:24:57 +00:00
|
|
|
gun_mount_anim.play("move")
|
2025-01-26 13:39:11 +00:00
|
|
|
if first_view_legs_anim != null:
|
|
|
|
first_view_legs_anim.play("Run Forward")
|
2025-01-25 08:03:06 +00:00
|
|
|
velocity.x = direction.x * SPEED
|
|
|
|
velocity.z = direction.z * SPEED
|
|
|
|
else:
|
2025-01-25 16:24:57 +00:00
|
|
|
if gun_mount_anim.is_playing():
|
|
|
|
gun_mount_anim.stop()
|
2025-01-26 13:39:11 +00:00
|
|
|
if first_view_legs_anim != null and first_view_legs_anim.is_playing():
|
|
|
|
first_view_legs_anim.stop()
|
2025-01-25 08:03:06 +00:00
|
|
|
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
|
|
velocity.z = move_toward(velocity.z, 0, SPEED)
|
2025-01-26 13:39:11 +00:00
|
|
|
if alive:
|
|
|
|
move_and_slide()
|
2025-01-25 08:03:06 +00:00
|
|
|
# -- TODO: It shouldn't be hardcoded
|
|
|
|
var bullet = load("res://scenes/weapon/bullet.tscn")
|
|
|
|
|
|
|
|
@onready var shooting_raycast: RayCast3D = $FirstPersonCameraMount/RayCast3D
|
|
|
|
@onready var bullet_starting_point: Node3D = $FirstPersonCameraMount/BulletStartingPoint
|
2025-01-26 09:59:59 +00:00
|
|
|
@onready var aim_ray: RayCast3D = $FirstPersonCameraMount/BulletStartingPoint/AimRay
|
2025-01-28 09:51:45 +00:00
|
|
|
var current_weapon_damage: int
|
|
|
|
var current_weapon_bullet_speed: int
|
|
|
|
var current_weapon_cooldown_interwal: float
|
2025-01-25 16:24:57 +00:00
|
|
|
# --find the gun node and exec shoot
|
2025-01-26 13:39:11 +00:00
|
|
|
var cant_shoot: bool = false
|
2025-01-25 08:03:06 +00:00
|
|
|
func _shoot():
|
2025-01-26 13:39:11 +00:00
|
|
|
if not cant_shoot:
|
|
|
|
_send_shot_to_server.rpc_id(1, aim_ray.global_position)
|
|
|
|
if aim_ray.is_colliding():
|
|
|
|
var collider := aim_ray.get_collider()
|
|
|
|
if collider != null and collider.is_in_group("target"):
|
|
|
|
aim_ray.get_collider().take_damage()
|
|
|
|
if collider != null and collider.is_in_group("body"):
|
2025-01-28 09:51:45 +00:00
|
|
|
collider.hit(50)
|
2025-01-26 13:39:11 +00:00
|
|
|
var root := get_tree().get_root()
|
|
|
|
gun_with_hands.shoot()
|
|
|
|
cant_shoot = true
|
2025-01-28 09:51:45 +00:00
|
|
|
await get_tree().create_timer(current_weapon_cooldown_interwal).timeout
|
2025-01-26 13:39:11 +00:00
|
|
|
cant_shoot = false
|
2025-01-26 09:59:59 +00:00
|
|
|
|
2025-01-26 13:39:11 +00:00
|
|
|
@rpc("any_peer", "call_local", "unreliable_ordered")
|
|
|
|
func _send_shot_to_server(start_position):
|
|
|
|
# -- TODO: Should not be hardcoded
|
|
|
|
var world: Node3D = find_parent("ElTest")
|
2025-01-28 09:51:45 +00:00
|
|
|
world.spawn_bullet(bullet_starting_point, current_weapon_bullet_speed, current_weapon_damage)
|
2025-01-26 13:39:11 +00:00
|
|
|
|
2025-01-26 09:59:59 +00:00
|
|
|
func _get_camera_collision():
|
|
|
|
var viewport = get_viewport().size
|
|
|
|
var ray_origin = first_view_camera.project_ray_origin(viewport / 2)
|
|
|
|
var ray_end = ray_origin + first_view_camera.project_ray_normal(viewport / 2 * 100)
|
|
|
|
var new_intersection = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
|
|
|
|
var intersection = get_world_3d().direct_space_state.intersect_ray(new_intersection)
|
|
|
|
if not intersection.is_empty():
|
|
|
|
var collision_point = intersection.position
|
|
|
|
print("gotcha")
|
|
|
|
return collision_point
|
|
|
|
else:
|
|
|
|
return ray_end
|
|
|
|
|
|
|
|
func _hit_scan_collision(collision_point):
|
|
|
|
var viewport = get_viewport().size
|
|
|
|
var ray_origin = first_view_camera.project_ray_origin(viewport / 2)
|
|
|
|
var ray_end = ray_origin + first_view_camera.project_ray_normal(viewport / 2 * 100)
|
|
|
|
var new_intersection = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
|
|
|
|
var bullet_collision = get_world_3d().direct_space_state.intersect_ray(new_intersection)
|
|
|
|
if bullet_collision:
|
|
|
|
print(bullet_collision)
|
|
|
|
_hit_scan_damage(bullet_collision.collider)
|
|
|
|
|
|
|
|
func _hit_scan_damage(Collider):
|
|
|
|
#if Collider.is_in_group("target") and Collider.has_method("take_damage"):
|
|
|
|
print("damaged")
|
2025-01-26 13:39:11 +00:00
|
|
|
|
|
|
|
func take_damage(dam: int):
|
|
|
|
var new_health = health - dam
|
|
|
|
set_health(new_health)
|
|
|
|
if multiplayer.is_server():
|
|
|
|
set_health.rpc(new_health)
|
|
|
|
|
|
|
|
@rpc("call_local", "reliable")
|
|
|
|
func set_health(val: int):
|
|
|
|
health = val
|