mirror of
https://gitlab.com/open-fpsz/open-fpsz.git
synced 2026-01-19 19:44:46 +00:00
249 lines
8.8 KiB
GDScript
249 lines
8.8 KiB
GDScript
# This file is part of open-fpsz.
|
|
#
|
|
# 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 3 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, see <https://www.gnu.org/licenses/>.
|
|
class_name Player extends RigidBody3D
|
|
|
|
enum PlayerState { PLAYER_ALIVE, PLAYER_DEAD }
|
|
|
|
@export var iff : Control
|
|
|
|
@export_category("Parameters")
|
|
@export var ground_speed : float = 48 / 3.6 # m/s
|
|
@export var aerial_control_force : int = 400
|
|
@export var jump_height : float = 2.0
|
|
@export var max_floor_angle : float = 60
|
|
|
|
@export_group("Jetpack")
|
|
@export var energy: float = 100.0
|
|
@export var energy_charge_rate : float = 20 # energy per second
|
|
@export var energy_drain_rate : float = 25 # energy per second
|
|
@export var energy_max : float = 100.
|
|
@export var jetpack_force_factor : float = 2.
|
|
@export var jetpack_horizontal_force : float = 600
|
|
@export var jetpack_vertical_force : float = 800
|
|
|
|
@export_group("State")
|
|
@export var player_state : PlayerState = PlayerState.PLAYER_ALIVE
|
|
|
|
@onready var input : PlayerInput = $PlayerInput
|
|
@onready var camera : Camera3D = $Smoothing/SpringArm3D/Camera3D
|
|
@onready var hud : CanvasLayer = $HUD
|
|
@onready var shape_cast : ShapeCast3D = $ShapeCast3D
|
|
@onready var weapon : Node3D = $Smoothing/SpringArm3D/Inventory/SpaceGun
|
|
@onready var animation_player : AnimationPlayer = $AnimationPlayer
|
|
@onready var health_component : Area3D = $HealthComponent
|
|
@onready var collision_shape : CollisionShape3D = $CollisionShape3D
|
|
@onready var flag_carry_component : FlagCarryComponent = $Smoothing/SpringArm3D/FlagCarryComponent
|
|
@onready var spring_arm_height : float = $Smoothing/SpringArm3D.position.y
|
|
@onready var _original_weapon_transform : Transform3D = weapon.transform
|
|
@onready var tp_player : Vanguard = $Smoothing/ThirdPerson/PlayerMesh
|
|
@onready var _game_settings : Settings = get_node("/root/GlobalSettings")
|
|
@onready var jetpack_particles : Array = $Smoothing/ThirdPerson/PlayerMesh/JetpackFX.get_children()
|
|
@onready var match_participant_component : MatchParticipantComponent = $MatchParticipantComponent
|
|
|
|
signal died(player : Player, killer_id : int)
|
|
signal energy_changed(energy : float)
|
|
|
|
static var pawn_player : Player
|
|
var g : float = ProjectSettings.get_setting("physics/3d/default_gravity") # in m/s²
|
|
var gravity : Vector3 = g * ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
|
var _jumping : bool = false
|
|
|
|
func _ready() -> void:
|
|
match_participant_component.player_id_changed.connect(_setup_pawn)
|
|
match_participant_component.player_id_changed.connect(input.update_multiplayer_authority)
|
|
match_participant_component.nickname_changed.connect(iff.set_nickname)
|
|
health_component.health_changed.connect(hud._on_health_changed)
|
|
health_component.health_changed.connect(iff._on_health_changed)
|
|
health_component.health_changed.emit(health_component.health)
|
|
health_component.health_zeroed.connect(die)
|
|
|
|
energy_changed.connect(hud._on_energy_changed)
|
|
input.fired_primary.connect(_fire_primary)
|
|
input.jumped.connect(_jump)
|
|
input.throwed_flag.connect(_throw_flag)
|
|
|
|
input.MOUSE_SENSITIVITY = _game_settings.mouse_sensitivity
|
|
input.inverted_y_axis = _game_settings.inverted_y_axis
|
|
|
|
func _setup_pawn(_new_player_id : int) -> void:
|
|
if _is_pawn():
|
|
camera.current = true
|
|
camera.fov = _game_settings.fov
|
|
pawn_player = self
|
|
# set the spring arm translation to be about head height level
|
|
$Smoothing/SpringArm3D.transform = Transform3D().translated(Vector3(0, collision_shape.shape.height / 2, 0) * 0.9)
|
|
$Smoothing/ThirdPerson.hide()
|
|
else:
|
|
# set the iff attachment translation to be about head height level
|
|
$Smoothing/ThirdPerson/IFFAttachment.transform = Transform3D().translated(Vector3(0, collision_shape.shape.height / 2, 0) * 0.9)
|
|
hud.hide()
|
|
weapon.hide()
|
|
|
|
func _is_pawn() -> bool:
|
|
return multiplayer.get_unique_id() == match_participant_component.player_id
|
|
|
|
func _fire_primary() -> void:
|
|
if _is_player_dead():
|
|
return
|
|
if not weapon.can_fire():
|
|
return
|
|
var current_weapon_transform : Transform3D = weapon.transform
|
|
weapon.transform = _original_weapon_transform
|
|
weapon.fire_primary()
|
|
weapon.transform = current_weapon_transform
|
|
|
|
func _jump() -> void:
|
|
if _is_player_dead():
|
|
return
|
|
_jumping = true
|
|
|
|
func _throw_flag() -> void:
|
|
flag_carry_component.throw(linear_velocity, self)
|
|
|
|
func is_on_floor() -> bool:
|
|
if shape_cast.is_colliding():
|
|
for i in shape_cast.get_collision_count():
|
|
var collider : Object = shape_cast.get_collider(i)
|
|
if collider is Terrain3D:
|
|
return true
|
|
return false
|
|
|
|
func _is_skiing() -> bool:
|
|
return input.skiing
|
|
|
|
func _handle_aerial_control(direction : Vector3) -> void:
|
|
if not input.jetting and not is_on_floor():
|
|
apply_force(direction * aerial_control_force)
|
|
|
|
func _handle_jetpack(direction : Vector3) -> void:
|
|
if input.jetting:
|
|
if energy > 0:
|
|
var up_vector : Vector3 = Vector3.UP * jetpack_vertical_force * jetpack_force_factor
|
|
var side_vector : Vector3 = direction * jetpack_horizontal_force * jetpack_force_factor
|
|
apply_force(up_vector + side_vector)
|
|
display_jetpack_particles()
|
|
|
|
func _update_jetpack_energy(delta : float) -> void:
|
|
if input.jetting:
|
|
if energy > 0:
|
|
energy -= energy_drain_rate * delta
|
|
else:
|
|
energy += energy_charge_rate * delta
|
|
|
|
energy = clamp(energy, 0, energy_max)
|
|
energy_changed.emit(energy)
|
|
|
|
func _process(_delta : float) -> void:
|
|
if _is_player_dead():
|
|
iff.hide()
|
|
return
|
|
else:
|
|
iff.show()
|
|
if not _is_pawn():
|
|
tp_player.global_transform.basis = Basis.from_euler(Vector3(0.0, input.camera_rotation.x + PI, 0.0))
|
|
elif not %Inventory/SpaceGun/Mesh/AnimationPlayer.is_playing():
|
|
%Inventory/SpaceGun/Mesh/AnimationPlayer.play("idle")
|
|
%SpringArm3D.global_transform.basis = Basis.from_euler(Vector3(input.camera_rotation.y, input.camera_rotation.x, 0.0))
|
|
|
|
func _physics_process(delta : float) -> void:
|
|
_update_jetpack_energy(delta)
|
|
|
|
func _handle_movement() -> void:
|
|
# retrieve user's direction vector
|
|
var _input_dir : Vector2 = input.direction
|
|
# compute direction in local space
|
|
var _direction : Vector3 = (transform.basis * Vector3(_input_dir.x, 0, _input_dir.y)).normalized()
|
|
|
|
_update_third_person_animations()
|
|
|
|
if _is_player_dead():
|
|
return
|
|
|
|
# adjust direction based on spring arm rotation
|
|
_direction = _direction.rotated(Vector3.UP, $Smoothing/SpringArm3D.rotation.y)
|
|
|
|
_handle_aerial_control(_direction)
|
|
_handle_jetpack(_direction)
|
|
|
|
# handle ski
|
|
if _is_skiing():
|
|
physics_material_override.friction = 0
|
|
else:
|
|
physics_material_override.friction = 1
|
|
|
|
if is_on_floor():
|
|
if not _direction.is_zero_approx() and not _is_skiing():
|
|
# retrieve collision normal
|
|
var normal : Vector3 = shape_cast.get_collision_normal(0)
|
|
# calculate the angle between the ground normal and the up vector
|
|
var slope_angle : float = rad_to_deg(acos(normal.dot(Vector3.UP)))
|
|
# check if the slope angle exceeds the maximum slope angle
|
|
if slope_angle <= max_floor_angle:
|
|
# adjust direction based on the floor normal to align with the slope
|
|
_direction = _direction.slide(normal)
|
|
|
|
linear_velocity = lerp(linear_velocity, _direction * ground_speed, .1)
|
|
|
|
if _jumping:
|
|
var v : float = sqrt(2 * g * jump_height)
|
|
apply_central_impulse(Vector3(0, mass * v, 0))
|
|
|
|
_jumping = false
|
|
|
|
func _integrate_forces(_state : PhysicsDirectBodyState3D) -> void:
|
|
_handle_movement()
|
|
|
|
func _update_third_person_animations() -> void:
|
|
if _is_pawn():
|
|
return
|
|
|
|
if _is_player_dead():
|
|
tp_player.set_ground_state(Vanguard.GroundState.GROUND_STATE_DEAD)
|
|
return
|
|
|
|
if is_on_floor():
|
|
tp_player.set_ground_state(Vanguard.GroundState.GROUND_STATE_GROUNDED)
|
|
else:
|
|
tp_player.set_ground_state(Vanguard.GroundState.GROUND_STATE_MID_AIR)
|
|
var local_velocity : Vector3 = (tp_player.global_basis.inverse() * linear_velocity)
|
|
const bias : float = 1.2 # Basically match feet speed with ground speed
|
|
tp_player.set_locomotion(Vector2(local_velocity.x, local_velocity.z), bias)
|
|
|
|
func _is_player_dead() -> bool:
|
|
return player_state != PlayerState.PLAYER_ALIVE
|
|
|
|
func die(killer_id : int) -> void:
|
|
flag_carry_component.drop(self)
|
|
player_state = PlayerState.PLAYER_DEAD
|
|
if _is_pawn():
|
|
animation_player.play("death")
|
|
died.emit(self, killer_id)
|
|
|
|
@rpc("call_local", "reliable")
|
|
func respawn(location : Vector3) -> void:
|
|
animation_player.stop()
|
|
player_state = PlayerState.PLAYER_ALIVE
|
|
linear_velocity = Vector3()
|
|
health_component.heal_full()
|
|
position = location
|
|
|
|
func _exit_tree() -> void:
|
|
player_state = PlayerState.PLAYER_DEAD
|
|
flag_carry_component.drop(self)
|
|
|
|
func display_jetpack_particles() -> void:
|
|
for particle: GPUParticles3D in jetpack_particles:
|
|
particle.emitting = true
|