2025-02-28 15:20:52 -07:00
|
|
|
class_name Player extends CharacterBody3D
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
#region Exported Properties
|
2025-03-28 16:16:43 -06:00
|
|
|
|
2025-04-19 10:11:10 -06:00
|
|
|
@export_category("Status")
|
|
|
|
@export var movement_enabled := true
|
|
|
|
@export var activity_enabled := true
|
|
|
|
@export var look_enabled := true
|
|
|
|
|
2025-04-19 12:08:16 -06:00
|
|
|
@export var camera_rumble: float:
|
|
|
|
set(value):
|
|
|
|
if Game.settings.enable_screen_shake:
|
|
|
|
cam_rumbler.intensity = value
|
|
|
|
get:
|
|
|
|
return cam_rumbler.intensity
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
@export_category("Movement")
|
|
|
|
@export_group("Speed")
|
|
|
|
@export var run_speed := 80.0
|
|
|
|
@export var sprint_speed := 160.0
|
|
|
|
@export var sneak_speed := 40.0
|
|
|
|
@export var focus_speed := 25.0
|
|
|
|
@export var air_speed_factor := 0.1
|
2025-03-28 16:16:43 -06:00
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
@export_group("Jump")
|
|
|
|
@export var jump_force := 4.0
|
2025-02-28 15:20:52 -07:00
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
@export_group("Friction")
|
|
|
|
@export var ground_friction := 0.3
|
|
|
|
@export var air_friction := 0.03
|
|
|
|
|
|
|
|
@export_category("Inventory")
|
2025-03-21 12:07:05 -06:00
|
|
|
@export var inventory: Dictionary[Item, int] = {}
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Member Variables
|
|
|
|
|
2025-02-28 15:20:52 -07:00
|
|
|
var gravity: Vector3 = (
|
|
|
|
ProjectSettings.get_setting("physics/3d/default_gravity")
|
|
|
|
* ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
|
|
|
)
|
|
|
|
|
2025-03-05 14:46:13 -07:00
|
|
|
var selected_interactive: Interactive
|
2025-03-05 16:11:03 -07:00
|
|
|
var firing := false
|
2025-04-04 14:40:42 -06:00
|
|
|
var sneaking := false
|
|
|
|
|
|
|
|
var _was_on_floor := false
|
2025-03-05 14:46:13 -07:00
|
|
|
|
2025-03-20 22:45:19 -06:00
|
|
|
@onready var hud: PlayerHUD = %PlayerHUD
|
2025-03-05 14:46:13 -07:00
|
|
|
|
2025-03-05 16:11:03 -07:00
|
|
|
@onready var camera_pivot: CameraController = %CameraPivot
|
2025-04-19 12:08:16 -06:00
|
|
|
@onready var cam_rumbler: Rumbler3D = %CamRumbler
|
2025-03-12 11:03:41 -06:00
|
|
|
|
2025-03-05 14:46:13 -07:00
|
|
|
@onready var interact_ray: RayCast3D = %InteractRay
|
2025-03-01 14:40:02 -07:00
|
|
|
|
2025-03-12 12:19:09 -06:00
|
|
|
@onready var tool_mount: ToolMount = %ToolMount
|
|
|
|
@onready var point_spray: PointSpray = %PointSpray
|
|
|
|
@onready var wide_spray: WideSpray = %WideSpray
|
2025-03-13 10:29:25 -06:00
|
|
|
@onready var toothbrush: Tool = %Toothbrush
|
2025-03-12 12:19:09 -06:00
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
@onready var crouch_head_area: Area3D = %CrouchHeadArea
|
|
|
|
@onready var crouch_animation: AnimationPlayer = %CrouchAnimation
|
2025-04-19 12:08:16 -06:00
|
|
|
@onready var grab_animation: AnimationPlayer = %GrabAnimation
|
2025-04-04 11:06:08 -06:00
|
|
|
|
2025-04-04 14:40:42 -06:00
|
|
|
@onready var jump_game_sound_emitter: GameSoundEmitter = %JumpGameSoundEmitter
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
#endregion
|
|
|
|
|
2025-03-20 22:45:19 -06:00
|
|
|
## Global static access to player singleton
|
|
|
|
static var instance: Player
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
#region _ready
|
|
|
|
|
2025-03-20 22:45:19 -06:00
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
instance = self
|
|
|
|
|
2025-02-28 15:20:52 -07:00
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Methods
|
|
|
|
|
|
|
|
|
2025-02-28 15:20:52 -07:00
|
|
|
func get_speed() -> float:
|
2025-04-04 11:06:08 -06:00
|
|
|
var speed := run_speed
|
|
|
|
if Input.is_action_pressed("sprint"):
|
|
|
|
speed = sprint_speed
|
2025-04-04 14:40:42 -06:00
|
|
|
if sneaking:
|
2025-04-04 11:06:08 -06:00
|
|
|
speed = sneak_speed
|
|
|
|
if firing:
|
|
|
|
speed = focus_speed
|
|
|
|
if not is_on_floor():
|
|
|
|
speed *= air_speed_factor
|
|
|
|
return speed
|
2025-02-28 15:20:52 -07:00
|
|
|
|
|
|
|
|
|
|
|
func get_friction() -> float:
|
|
|
|
if is_on_floor():
|
2025-04-04 11:06:08 -06:00
|
|
|
return ground_friction
|
|
|
|
return air_friction
|
2025-02-28 15:20:52 -07:00
|
|
|
|
|
|
|
|
2025-03-12 12:19:09 -06:00
|
|
|
func get_tool() -> Tool:
|
|
|
|
return tool_mount.get_active()
|
2025-03-01 14:40:02 -07:00
|
|
|
|
|
|
|
|
2025-03-21 12:07:05 -06:00
|
|
|
## Add the given item to the player's inventory.
|
|
|
|
func add_item(item: Item, amount: int = 1) -> void:
|
|
|
|
inventory[item] = inventory.get(item, 0) + amount
|
|
|
|
|
|
|
|
|
|
|
|
## Remove the given item from the player's inventory.
|
|
|
|
func remove_item(item: Item, amount: int = 1) -> void:
|
|
|
|
inventory[item] = inventory.get(item, 0) - amount
|
|
|
|
if inventory[item] <= 0:
|
|
|
|
inventory.erase(item)
|
|
|
|
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
func crouch() -> void:
|
2025-04-04 14:40:42 -06:00
|
|
|
if not sneaking and not crouch_animation.is_playing():
|
2025-04-04 11:06:08 -06:00
|
|
|
crouch_animation.play("crouch")
|
2025-04-04 14:40:42 -06:00
|
|
|
sneaking = true
|
2025-04-04 11:06:08 -06:00
|
|
|
|
|
|
|
|
|
|
|
func uncrouch() -> void:
|
|
|
|
if (
|
2025-04-04 14:40:42 -06:00
|
|
|
sneaking
|
2025-04-04 11:06:08 -06:00
|
|
|
and not crouch_animation.is_playing()
|
|
|
|
and not crouch_head_area.has_overlapping_bodies()
|
|
|
|
):
|
|
|
|
crouch_animation.play_backwards("crouch")
|
2025-04-04 14:40:42 -06:00
|
|
|
sneaking = false
|
2025-04-04 11:06:08 -06:00
|
|
|
|
|
|
|
|
|
|
|
func toggle_crouch() -> void:
|
2025-04-04 14:40:42 -06:00
|
|
|
if sneaking:
|
2025-04-04 11:06:08 -06:00
|
|
|
uncrouch()
|
|
|
|
else:
|
|
|
|
crouch()
|
|
|
|
|
|
|
|
|
2025-04-19 12:08:16 -06:00
|
|
|
## Get fuckign grabbed, idiot!
|
|
|
|
## Begin grab death sequence animation.
|
|
|
|
func get_grabbed(look_target: Vector3) -> void:
|
|
|
|
movement_enabled = false
|
|
|
|
activity_enabled = false
|
|
|
|
look_enabled = false
|
|
|
|
|
|
|
|
uncrouch()
|
|
|
|
grab_animation.play("get_grabbed")
|
|
|
|
camera_pivot.reset_pitch(0.4)
|
|
|
|
|
|
|
|
|
2025-04-04 11:06:08 -06:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region _physics_process
|
|
|
|
|
|
|
|
|
2025-02-28 15:20:52 -07:00
|
|
|
func _physics_process(delta: float) -> void:
|
2025-04-19 12:08:16 -06:00
|
|
|
# REMOVEME
|
|
|
|
if Input.is_action_just_pressed("ui_page_down"):
|
|
|
|
get_grabbed(Vector3.FORWARD)
|
|
|
|
if Input.is_action_just_pressed("ui_page_up"):
|
|
|
|
movement_enabled = true
|
|
|
|
activity_enabled = true
|
|
|
|
look_enabled = true
|
|
|
|
grab_animation.play("RESET")
|
|
|
|
|
2025-03-05 15:35:49 -07:00
|
|
|
# Will be null if no valid interactor is selected.
|
|
|
|
var interactive: Interactive = interact_ray.get_collider() as Interactive
|
2025-03-20 22:45:19 -06:00
|
|
|
hud.select_interactive(interactive)
|
2025-03-21 12:07:05 -06:00
|
|
|
if interactive:
|
|
|
|
interactive.select()
|
2025-03-05 15:35:49 -07:00
|
|
|
|
2025-04-19 10:11:10 -06:00
|
|
|
if activity_enabled:
|
|
|
|
# World interaction
|
|
|
|
if interactive and Input.is_action_just_pressed("interact"):
|
|
|
|
interactive.activate()
|
|
|
|
|
|
|
|
# Tool selection
|
|
|
|
if Input.is_action_just_pressed("select_next_tool"):
|
|
|
|
tool_mount.set_active_relative(1)
|
|
|
|
elif Input.is_action_just_pressed("select_prev_tool"):
|
|
|
|
tool_mount.set_active_relative(-1)
|
|
|
|
elif Input.is_action_just_pressed("select_point_spray"):
|
|
|
|
tool_mount.set_active(point_spray)
|
|
|
|
elif Input.is_action_just_pressed("select_wide_spray"):
|
|
|
|
tool_mount.set_active(wide_spray)
|
|
|
|
elif Input.is_action_just_pressed("select_brush"):
|
|
|
|
tool_mount.set_active(toothbrush)
|
|
|
|
|
|
|
|
# Tool use
|
|
|
|
if Input.is_action_pressed("fire"):
|
|
|
|
get_tool().fire()
|
|
|
|
firing = true
|
2025-04-04 11:06:08 -06:00
|
|
|
else:
|
2025-04-19 10:11:10 -06:00
|
|
|
get_tool().idle()
|
|
|
|
firing = false
|
|
|
|
|
|
|
|
if Input.is_action_just_pressed("switch_mode"):
|
|
|
|
get_tool().switch_mode()
|
|
|
|
|
|
|
|
# Two sneaking modes -- hold and toggle
|
|
|
|
if Game.settings.hold_to_sneak:
|
|
|
|
if Input.is_action_pressed("sneak"):
|
|
|
|
crouch()
|
|
|
|
else:
|
|
|
|
uncrouch()
|
|
|
|
else:
|
|
|
|
if Input.is_action_just_pressed("sneak"):
|
|
|
|
toggle_crouch()
|
|
|
|
|
|
|
|
# Jumping
|
|
|
|
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
|
|
velocity.y = jump_force
|
2025-04-04 11:06:08 -06:00
|
|
|
|
2025-04-04 14:40:42 -06:00
|
|
|
if is_on_floor():
|
|
|
|
if not _was_on_floor and not sneaking:
|
|
|
|
# just landed
|
|
|
|
jump_game_sound_emitter.emit_sound_here()
|
|
|
|
else:
|
|
|
|
# Gravity
|
2025-02-28 15:20:52 -07:00
|
|
|
velocity += gravity * delta
|
|
|
|
|
|
|
|
# Input movement
|
2025-04-19 10:11:10 -06:00
|
|
|
if movement_enabled:
|
|
|
|
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
|
|
|
var rel_input := input_dir.rotated(-camera_pivot.global_rotation.y)
|
|
|
|
var direction := Vector3(rel_input.x, 0, rel_input.y).normalized()
|
|
|
|
var movement := direction * get_speed() * delta
|
|
|
|
velocity.x += movement.x
|
|
|
|
velocity.z += movement.z
|
2025-02-28 15:20:52 -07:00
|
|
|
|
|
|
|
# Friction
|
|
|
|
var friction := get_friction()
|
2025-04-18 15:52:31 -06:00
|
|
|
var weight := 1 - exp(-friction * 60 * delta)
|
|
|
|
velocity.x = lerpf(velocity.x, 0, weight)
|
|
|
|
velocity.z = lerpf(velocity.z, 0, weight)
|
2025-02-28 15:20:52 -07:00
|
|
|
|
2025-04-04 14:40:42 -06:00
|
|
|
_was_on_floor = is_on_floor()
|
|
|
|
|
2025-02-28 15:20:52 -07:00
|
|
|
move_and_slide()
|
2025-04-04 11:06:08 -06:00
|
|
|
|
|
|
|
#endregion
|