extends CharacterBody3D
# NOTICE all comments for work:
#Critical comments: ALERT,ATTENTION,CAUTION,CRITICAL,DANGER,SECURITY
#Warning comments: BUG,DEPRECATED,FIXME,HACK,TASK,TBD,TODO,WARNING
#Notices: INFO,NOTE,NOTICE,TEST,TESTING
@onready var title = "Prometheus Pre-Alpha"
@onready var neck: Node3D = $Neck
@onready var head: Node3D = $Neck/Head
@onready var eye: Node3D = $Neck/Head/Eye
@onready var camera: Camera3D = $Neck/Head/Eye/Camera
@onready var shoulder: Node3D = $Shoulder
@onready var hand: Node3D = $Shoulder/Hand
@onready var flashlight: SpotLight3D = $Shoulder/Hand/Flashlight
@onready var collision_shape: CollisionShape3D = $CollisionShape
@onready var top_cast: ShapeCast3D = $TopCast
@onready var interaction_cast: RayCast3D = $"Neck/Head/Eye/Camera/Interaction cast"
@onready var fps: Label = $"Neck/Head/Eye/Camera/2D overlays/Control/FPS"
@onready var animation_player: AnimationPlayer = $"Neck/Head/Eye/Camera/2D
overlays/CanvasLayer/AnimationPlayer"
@export_category("Movement")
@export var walkSpeed = 5.0
@export var sprintSpeed = 7.0
@export var staminaMax = 6.0
@export var staminaPenalty = 10.0
@export var crouchspeed = 3.0
@export var playerAcceleration = 8.0
@export var airFriction = 1.0
@export var jumpForce = 7.0
@export_category("Visuals")
@export var crouchHeight = 1.0
@export var crouchTransition = 8.0
@export_exp_easing("attenuation") var lerpSpd = 10.0
@export var cameraBaseFOV = 60.0
@export var mouseSens = 0.4
@export var camAcceleration = 10.0
@export_group("Head Bob")
@export var head_bobbing_crouch_speed = 9.0
@export var head_bobbing_walk_speed = 12.0
@export var head_bobbing_sprint_speed = 15.0
@export var head_bobbing_crouch_intensity = 0.1
@export var head_bobbing_sprint_intensity = 0.3
@export var head_bobbing_walk_intensity = 0.2
@export_group("flashlight")
@export_subgroup("Inside")
@export var flashlightInnerEnergy = 2.0
@export var flashlightInnerFogEnergy = 16.0
@export var flashlightInnerRange = 20.0
@export var flashlightInnerAngle = 15.0
@export_subgroup("Outside")
@export var flashlightOuterEnergy = 0.5
@export var flashlightOuterFogEnergy = 8.0
@export var flashlightOuterRange = 20.0
@export var flashlightOuterAngle = 30.0
@export var inventory : Array
@export var inventorySize : int = 5
enum MovementState {
Crouching,
Sprinting,
Walking
}
var i = 0
var isReady : bool
var standHeight : float
var CurrentState: MovementState = MovementState.Walking
var local_velocity
var gravity : Vector3
var headYaxis : float
var camXaxis : float
var currentSpeed : float
var stamina : float
var head_bobbing_vector = Vector2.ZERO
var head_bobbing_rotation = 0.0
var head_bobbing_index = 0.0
var head_bobbing_current_intensity = 0.0
var direction := Vector3.ZERO
const maxStepHeight = 0.5
var snappedToStairLastFrame := false
var lastFrameWasOnFloor = -INF
func _ready() -> void:
standHeight = collision_shape.shape.height
stamina = staminaMax
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(event: InputEvent) -> void:
handleCameraMovementEvents(event)
func isSurfaceTooSteep(normal : Vector3) -> bool:
return normal.angle_to(Vector3.UP) > self.floor_max_angle
func runBodyTestMotion(from : Transform3D, motion : Vector3, result = null) ->
bool:
if not result : result = PhysicsTestMotionResult3D.new()
var params = PhysicsTestMotionParameters3D.new()
params.from = from
params.motion = motion
return PhysicsServer3D.body_test_motion(self.get_rid(), params, result)
func snapUpStairsCheck(delta) -> bool:
if not is_on_floor() and not snappedToStairLastFrame: return false
var expectedMoveMotion = self.velocity * Vector3(1,0,1) * delta
var stepPosWithClearance =
self.global_transform.translated(expectedMoveMotion + Vector3(0, maxStepHeight * 2,
0))
var downCheckResult = PhysicsTestMotionResult3D.new()
if (runBodyTestMotion(stepPosWithClearance, Vector3(0,-maxStepHeight * 2,0),
downCheckResult)
and (downCheckResult.get_collider().is_class("StaticBody3D") or
downCheckResult.get_collider().is_class("CSGShape3D"))):
var stepHeight = ((stepPosWithClearance.origin +
downCheckResult.get_travel()) - self.global_position).y
if stepHeight > maxStepHeight or stepHeight <= 0.01 or
(downCheckResult.get_collision_point() - self.global_position).y > maxStepHeight:
return false
%StairAheadRayCast.global_position =
downCheckResult.get_collision_point() + Vector3(0, maxStepHeight, 0) +
expectedMoveMotion.normalized() * 0.1
%StairAheadRayCast.force_raycast_update()
if %StairAheadRayCast.is_colliding() and not
isSurfaceTooSteep(%StairAheadRayCast.get_collision_normal()):
self.global_position = stepPosWithClearance.origin +
downCheckResult.get_travel()
apply_floor_snap()
snappedToStairLastFrame = true
saveCameraGlobalPosForsmoothing()
return true
return false
func snapDownToStairCheck() -> void:
var didSnap = false
var floorBelow : bool = %StairBelowRayCast.is_colliding() and not
isSurfaceTooSteep(%StairBelowRayCast.get_collision_normal())
var wasOnFloorLastFrame = Engine.get_physics_frames() - lastFrameWasOnFloor
== 1
if not is_on_floor() and velocity.y <= 0 and (wasOnFloorLastFrame or
snappedToStairLastFrame) and floorBelow:
var bodyTestResult = PhysicsTestMotionResult3D.new()
if runBodyTestMotion(self.global_transform, Vector3(0, -maxStepHeight,
0), bodyTestResult):
var translateY = bodyTestResult.get_travel().y
self.position.y += translateY
apply_floor_snap()
saveCameraGlobalPosForsmoothing()
didSnap = true
snappedToStairLastFrame = didSnap
var savedCameraGlobalPos = null
func saveCameraGlobalPosForsmoothing():
if savedCameraGlobalPos == null:
savedCameraGlobalPos = %Eye.global_position
func slideEyeBackToOrigin(delta):
if savedCameraGlobalPos == null: return
%Eye.global_position.y = savedCameraGlobalPos.y
%Eye.position.y = clampf(%Eye.position.y, -0.7, 0.7)
var moveAmount = max(self.velocity.length() * delta, walkSpeed/2 * delta)
%Eye.position.y = move_toward(%Eye.position.y, 0.0, moveAmount)
savedCameraGlobalPos = %Eye.global_position
if %Eye.position.y == 0:
savedCameraGlobalPos = null
func _physics_process(delta: float) -> void:
handleMouseModes()
displayFPS()
inventory.resize(inventorySize)
#local velocity calculation
local_velocity = Vector3(velocity.dot(head.basis.x),
velocity.dot(head.basis.y), velocity.dot(head.basis.z))
if is_on_floor:
lastFrameWasOnFloor = Engine.get_physics_frames()
#get gravity
gravity = get_gravity()
handleInteraction()
handleCameraMovement(delta)
handlePlayerInput(delta)
handleCurrentPlayerState(delta)
dynamicHeadTilt(local_velocity, delta)
dynamicFOV(local_velocity, delta)
movement(delta)
headBob(delta)
if not snapUpStairsCheck(delta):
move_and_slide()
snapDownToStairCheck()
slideEyeBackToOrigin(delta)
func handleInventory():
pass # TODO
func displayFPS():
fps.text = "fps: " + str(Engine.get_frames_per_second())
func crouch(delta : float, reverse = false):
var targetHeight : float = crouchHeight if not reverse else standHeight
collision_shape.shape.height = lerp(collision_shape.shape.height,
targetHeight, delta * crouchTransition)
collision_shape.position.y = lerp(collision_shape.position.y, targetHeight *
0.5, delta * crouchTransition)
head.position.y = lerp(head.position.y, targetHeight - 1, delta *
crouchTransition)
shoulder.position.y = lerp(shoulder.position.y, targetHeight - 0.5, delta *
crouchTransition)
func dynamicHeadTilt(local_velocity : Vector3, delta : float):
#head tilt on strafe
if local_velocity.x != 0 && is_on_floor():#2.85
head.rotation.z = clamp(lerp(head.rotation.z, deg_to_rad(-
local_velocity.x/2.85), delta * 15.0), deg_to_rad(-10), deg_to_rad(10))
else:
head.rotation.z = lerp(head.rotation.z, 0.0, delta * 5.0)
#head tilt on jump and fall
if local_velocity.y != 0 && !snappedToStairLastFrame: #1.3
head.rotation.x = clamp(lerp(head.rotation.x, deg_to_rad(-
local_velocity.y/1.3), delta * lerpSpd), deg_to_rad(-30), deg_to_rad(30))
else:
head.rotation.x = lerp(head.rotation.x, 0.0, delta * lerpSpd)
func dynamicFOV(local_velocity : Vector3, delta : float):
#dynamic fov
if local_velocity.z < 0 && direction != Vector3.ZERO: #1.2
camera.fov = lerp(camera.fov, cameraBaseFOV - local_velocity.z *
1.2,delta * 5.0)
else:
camera.fov = lerp(camera.fov, cameraBaseFOV, delta * 5.0)
func movement(delta : float):
#input management
direction = (Input.get_axis("left","right") * head.basis.x +
Input.get_axis("up", "down") * head.basis.z).normalized()
#movement
if is_on_floor() or snappedToStairLastFrame == true:
velocity = velocity.lerp(direction * currentSpeed + velocity.y *
Vector3.UP, playerAcceleration * delta)
else:
velocity = velocity.lerp(direction * currentSpeed + velocity.y *
Vector3.UP, airFriction * delta)
func headBob(delta : float):
if (is_on_floor() or snappedToStairLastFrame and is_on_floor()) &&
direction != Vector3.ZERO:
head_bobbing_vector.y = sin(head_bobbing_index)
head_bobbing_vector.x = sin(head_bobbing_index/2)+0.5
head_bobbing_rotation = sin(head_bobbing_index/2)
head.rotation.z = lerp(head.rotation.z,
deg_to_rad(head_bobbing_rotation + head_bobbing_current_intensity), delta *
lerpSpd)
eye.position.y = lerp(eye.position.y,
head_bobbing_vector.y*(head_bobbing_current_intensity/2.0), delta * lerpSpd)
eye.position.x = lerp(eye.position.x,
head_bobbing_vector.x*head_bobbing_current_intensity, delta * lerpSpd)
else:
head.rotation.z = lerp(head.rotation.z, 0.0, delta * lerpSpd)
eye.position.y = lerp(eye.position.y, 0.0, delta * lerpSpd)
eye.position.x = lerp(eye.position.x, 0.0, delta * lerpSpd)
func handlePlayerInput(delta : float):
#Movement State Management
if Input.is_action_pressed("jump") and (is_on_floor()):
velocity.y = jumpForce
else:
velocity += gravity * delta
if Input.is_action_pressed("crouch"):
CurrentState = MovementState.Crouching
elif not top_cast.is_colliding():
if Input.is_action_pressed("sprint") && isReady:
CurrentState = MovementState.Sprinting
else:
CurrentState = MovementState.Walking
func handleCurrentPlayerState(delta : float):
#State Machine For Movement
match CurrentState:
MovementState.Crouching:
currentSpeed = lerp(currentSpeed, crouchspeed, delta *
playerAcceleration)
head_bobbing_current_intensity = head_bobbing_crouch_intensity
head_bobbing_index += head_bobbing_crouch_speed*delta
crouch(delta)
MovementState.Sprinting:
currentSpeed = lerp(currentSpeed, sprintSpeed, delta *
playerAcceleration)
head_bobbing_current_intensity = head_bobbing_sprint_intensity
head_bobbing_index += head_bobbing_sprint_speed*delta
crouch(delta, true)
MovementState.Walking:
currentSpeed = lerp(currentSpeed, walkSpeed, delta *
playerAcceleration)
head_bobbing_current_intensity = head_bobbing_walk_intensity
head_bobbing_index += head_bobbing_walk_speed*delta
crouch(delta, true)
func handleCameraMovementEvents(event):
if event is InputEventMouseMotion:
headYaxis += event.relative.x * mouseSens
camXaxis += event.relative.y * mouseSens
camXaxis = clamp(camXaxis, -85, 85)
func handleMouseModes():
if Input.is_action_just_pressed("show mouse"):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
Engine.time_scale = 0.0
elif Input.mouse_mode == Input.MOUSE_MODE_VISIBLE &&
Input.is_action_just_pressed("hide mouse"):
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func handleCameraMovement(delta : float):
#Camera Movement
head.rotation.y = lerp(head.rotation.y, -deg_to_rad(headYaxis),
camAcceleration * delta)
camera.rotation.x = lerp(camera.rotation.x, -deg_to_rad(camXaxis),
camAcceleration * delta)
camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-80), deg_to_rad(80))
#flashlight movement
shoulder.rotation.y = -deg_to_rad(headYaxis)
hand.rotation.x = -deg_to_rad(camXaxis)
var Obj : Node
var interacting = false
func handleInteraction():
if interaction_cast.is_colliding() &&
Input.is_action_just_pressed("interact"):
var object = interaction_cast.get_collider()
Obj = object
print(object)
print(interacting)
if object is interactable:
if object is Lever:
object.interact(self)
interacting = true
elif object is button:
object.interact(self)
interacting = false
elif object is Pickupable:
object.interact(self)
interacting = false
else:
interacting = false
elif Input.is_action_just_released("interact"):
interacting = false
var pressed : bool