Files
blorgshooter/scripts/character.gd
2025-08-12 04:38:42 -04:00

438 lines
14 KiB
GDScript

extends CharacterBody3D
# TODO: Add descriptions for each value
@export_category("Character")
@export var start_rotation : float = 0
var start_rotated : bool = false
@export var max_health : float = 100.0
var hp : float
var playerallied : bool = true
var currentgun : String = "pistol"
@export var base_speed : float = 10.0
@export var sprint_speed : float = 6.0
@export var crouch_speed : float = 6.0
@export var acceleration : float = 10.0
@export var jump_velocity : float = 6.0
@export var mouse_sensitivity : float = 0.1
@export var walljump_cooldown : float = 0.75
@export var wallbounce_multiplier : float = 0.3
@export var aircontrol_multiplier : float = 0.5
@export var aircontrol_delay : float = 0.5
@export var coyotetime : float = 0.3
var coyotechance : bool = false
@export var dash_cooldown : float = 2.0
@export var dash_speedmultiplier : float = 3.0
@export var dash_distance : float = 5.0
var dash_spent : bool = false
var dash_active : bool = false
var dash_startpos : Vector3 = Vector3(0, 0, 0)
@export var physics_push_force : float = 1.0
var holding_object : bool = false
var held_object : RigidBody3D
@export var object_hurl_force : float = 40.0
@export var object_grab_distance : float = 8.0
@export var object_hold_height : float = 1.0
@export var gun_damage : float = 6
@export var gun_firedelay : float = 0.2
@export var gun_physics_nudge_multiplier : float = 5
var stomping : bool = false
var stomp_startingheight : float
var stomp_speed : float = 25
var stomp_radius : float = 6
var stomp_radiusthreshold : float = 2
var stomp_radiusincrement : float = 1
var stomp_maxradius : float = 12
var stomp_damagepermeter : float = 3
var stomp_mindamage : float = 4
var stomp_maxdamage : float = 16
@export_group("Nodes")
@export var HEAD : Node3D
@export var CAMERA : Camera3D
@export var CAMERA_ANIMATION : AnimationPlayer
@export_group("Controls")
# We are using UI controls because they are built into Godot Engine so they can be used right away
@export var JUMP : String = "move_jump"
@export var LEFT : String = "move_left"
@export var RIGHT : String = "move_right"
@export var FORWARD : String = "move_forward"
@export var BACKWARD : String = "move_backward"
@export var PAUSE : String = "ui_cancel"
@export var CROUCH : String = "move_crouch"
@export var DASH : String = "move_dash"
@export var TELEK : String = "action_telekinesis"
@export var SHOOT : String = "action_shoot"
@export var SPRINT : String
@export_group("Feature Settings")
@export var immobile : bool = false
@export var jumping_enabled : bool = true
@export var in_air_momentum : bool = true
@export var motion_smoothing : bool = true
@export var sprint_enabled : bool = false
@export var crouch_enabled : bool = true
@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
@export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0
@export var dynamic_fov : bool = false
@export var continuous_jumping : bool = false
@export var view_bobbing : bool = false
# Member variables
var speed : float = base_speed
var is_crouching : bool = false
var is_sprinting : bool = false
var walljump_ready : bool = true
var enemies : Array[Node] = []
# Get the gravity from the project settings to be synced with RigidBody nodes
var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process
const tex_pistol_neutral = preload("res://sprites/fpguns/pistol_neutral.png")
const tex_pistol_recoil = preload("res://sprites/fpguns/pistol_recoil.png")
const tex_telehand_grip = preload("res://sprites/fpguns/telehand_grip.png")
const tex_telehand_pull = preload("res://sprites/fpguns/telehand_pull.png")
const tex_telehand_throw = preload("res://sprites/fpguns/telehand_throw.png")
func _ready():
HEAD = $Head
CAMERA = $Head/Camera
CAMERA_ANIMATION = $Head/camera_animation
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
$Head/Camera/Ray.target_position.z -= 1024
$Head/TelekinesisPoint.position.z -= object_grab_distance / 2
$Head/TelekinesisPoint.position.y += object_hold_height
hp = max_health
$UserInterface/Telehand.visible = false
$stomparea.scale = Vector3(stomp_radius, stomp_radius, stomp_radius)
func _physics_process(delta):
if Input.is_action_just_pressed("exit"):
get_tree().quit()
$UserInterface/HUDContainer/HUDText.update(self,$walljump_timer,$dash_timer)
if !start_rotated:
HEAD.rotation_degrees.y += start_rotation
start_rotated = true
# Add some debug data
$UserInterface/DebugPanel.add_property("Movement Speed", speed, 1)
$UserInterface/DebugPanel.add_property("Velocity", get_real_velocity(), 2)
# Gravity
#gravity = ProjectSettings.get_setting("physics/3d/default_gravity") # If the gravity changes during your game, uncomment this code
if not is_on_floor():
if velocity.y >= gravity:
velocity.y = gravity
velocity.y -= gravity * delta
handle_jumping()
var input_dir = Vector2.ZERO
if !immobile:
input_dir = Input.get_vector(LEFT, RIGHT, FORWARD, BACKWARD)
handle_movement(delta, input_dir)
handle_stomp()
handle_telekinesis()
handle_gunshoot()
# toggle_crouch()
toggle_sprint(input_dir)
if Input.is_action_just_pressed(DASH) and !dash_spent:
dash_spent = true
dash_startpos = position
dash_active = true
$dash_timer.start(dash_cooldown)
$aircontrol_timer.stop()
if dash_active and dash_startpos.distance_to(position) > dash_distance:
dash_active = false
if dash_active and input_dir.x == 0 and input_dir.y == 0:
dash_active = false
if dash_active:
speed = base_speed * dash_speedmultiplier
elif is_crouching:
speed = crouch_speed
elif is_sprinting:
speed = sprint_speed
else:
speed = base_speed
if view_bobbing:
headbob_animation(input_dir)
for i in get_slide_collision_count():
var slaminto = get_slide_collision(i)
if slaminto.get_collider() is RigidBody3D:
slaminto.get_collider().apply_central_impulse(-slaminto.get_normal() * physics_push_force)
func handle_jumping():
if jumping_enabled:
if !coyotechance and !is_on_floor():
$coyote_timer.start(coyotetime)
coyotechance = true
if continuous_jumping:
if Input.is_action_pressed(JUMP) and is_on_floor():
velocity.y += jump_velocity
else:
if Input.is_action_just_pressed(JUMP):
if is_on_floor() or (coyotechance and !$coyote_timer.is_stopped()):
velocity.y += jump_velocity
$aircontrol_timer.start(aircontrol_delay)
if walljump_ready and Input.is_action_just_pressed(JUMP) and !is_on_floor() and is_on_wall():
velocity.x = velocity.x + ((position.bounce(get_slide_collision(0).get_normal()).x) * wallbounce_multiplier)
velocity.z = velocity.z + ((position.bounce(get_slide_collision(0).get_normal()).z) * wallbounce_multiplier)
velocity.y += jump_velocity
walljump_ready = false
$walljump_timer.start(walljump_cooldown)
$aircontrol_timer.start(aircontrol_delay)
print("walljump timer started")
func handle_movement(delta, input_dir):
var direction = input_dir.rotated(-HEAD.rotation.y)
direction = Vector3(direction.x, 0, direction.y)
move_and_slide()
if in_air_momentum:
if is_on_floor(): # Don't lerp y movement
if motion_smoothing:
velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
else:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
else:
if direction.x != 0 and direction.z != 0 and $aircontrol_timer.is_stopped():
if motion_smoothing:
velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
else:
if motion_smoothing:
velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
else:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
func _process(delta):
$UserInterface/DebugPanel.add_property("FPS", 1.0/delta, 0)
if Input.is_action_just_pressed(PAUSE):
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
elif Input.mouse_mode == Input.MOUSE_MODE_VISIBLE:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event):
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
HEAD.rotation_degrees.y -= event.relative.x * mouse_sensitivity
HEAD.rotation_degrees.x -= event.relative.y * mouse_sensitivity
HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
"""func toggle_crouch():
if crouch_enabled:
if crouch_mode == 0:
is_crouching = Input.is_action_pressed(CROUCH)
elif crouch_mode == 1:
if Input.is_action_just_pressed(CROUCH):
is_crouching = !is_crouching
# Replace with your own crouch animation code
if is_crouching:
$Collision.scale.y = lerp($Collision.scale.y, 0.75, 0.2)
else:
$Collision.scale.y = lerp($Collision.scale.y, 1.0, 0.2)"""
func toggle_sprint(moving):
if sprint_enabled:
if sprint_mode == 0:
if !is_crouching: # Crouching takes priority over sprinting
is_sprinting = Input.is_action_pressed(SPRINT)
else:
is_sprinting = false # Fix a bug where if you are sprinting and then crouch then let go of the sprinting button you keep sprinting
elif sprint_mode == 1:
if Input.is_action_just_pressed(SPRINT):
if !is_crouching:
is_sprinting = !is_sprinting
else:
is_sprinting = false
if dynamic_fov:
if is_sprinting and moving:
CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3)
else:
CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3)
func headbob_animation(moving):
if moving and is_on_floor():
CAMERA_ANIMATION.play("headbob")
CAMERA_ANIMATION.speed_scale = speed / base_speed
else:
CAMERA_ANIMATION.play("RESET")
func handle_telekinesis():
if holding_object:
held_object.global_position = held_object.global_position.lerp($Head/TelekinesisPoint.global_position, 0.3)
if $UserInterface/Telehand.texture == tex_telehand_pull and held_object.global_position.distance_to($Head/TelekinesisPoint.global_position) < 0.1:
$UserInterface/Telehand.texture = tex_telehand_grip
if Input.is_action_just_pressed(CROUCH) and holding_object:
holding_object = false
held_object.gravity_scale = 1.0
$UserInterface/Telehand.texture = tex_telehand_throw
$telehand_timer.start(0.6)
elif Input.is_action_just_pressed(TELEK):
if !holding_object:
var object = $Head/Camera/Ray.get_collider()
if object != null and ($Head/Camera/Ray.get_collision_point().distance_to(position) <= object_grab_distance) and object.get("grabbable") == true:
holding_object = true
held_object = object
held_object.gravity_scale = 0.0
$UserInterface/Telehand.texture = tex_telehand_pull
$UserInterface/Telehand.visible = true
$telehand_timer.stop()
$telehand_timer.start(0.5)
else:
held_object.apply_central_impulse($Head/Camera/Ray.global_position.direction_to(held_object.global_position - Vector3(0,object_hold_height,0)) * Vector3(object_hurl_force, object_hurl_force, object_hurl_force))
holding_object = false
held_object.gravity_scale = 1.0
$UserInterface/Telehand.texture = tex_telehand_throw
$telehand_timer.start(0.5)
func handle_gunshoot():
if Input.is_action_just_pressed(SHOOT) and $gunfire_timer.is_stopped():
$gunfire_timer.start(gun_firedelay)
$gunflash_timer.start(0.08)
$UserInterface/Gun.texture = tex_pistol_recoil
var object = $Head/Camera/Ray.get_collider()
if object != null:
if object.get("shootable") == true:
var multiplier : float = 1.0
if object.get("weakpoint") == true:
if object.rootnode:
multiplier = object.weakpoint_damage
else:
multiplier = object.owner.weakpoint_damage
if object.rootnode:
object.deal_damage(gun_damage * multiplier)
else:
object.owner.deal_damage(gun_damage * multiplier)
elif object.get("grabbable") == true:
var shootforce = gun_damage * gun_physics_nudge_multiplier
object.apply_central_impulse($Head/Camera/Ray.global_position.direction_to(object.global_position) * Vector3(shootforce, shootforce, shootforce))
if object == held_object:
holding_object = false
held_object.gravity_scale = 1.0
$UserInterface/Telehand.visible = false
$telehand_timer.stop
func handle_stomp():
if !stomping and !is_on_floor() and !holding_object and Input.is_action_just_pressed(CROUCH):
stomping = true
stomp_startingheight = global_position.y
$stomparea.monitoring = true
if stomping:
if !is_on_floor():
velocity.y = -stomp_speed
var stompheight = stomp_startingheight - global_position.y
if stompheight >= stomp_radiusthreshold:
var newradius = clamp(stomp_radius + (stomp_radiusincrement * (stompheight - stomp_radiusthreshold)), stomp_radius, stomp_maxradius)
$stomparea.scale = Vector3(newradius, newradius, newradius)
else:
stomping = false
var damage = clamp(stomp_damagepermeter * (stomp_startingheight - global_position.y), stomp_mindamage, stomp_maxdamage)
for body in $stomparea.get_overlapping_bodies():
if body is RigidBody3D:
body.apply_central_impulse(global_position.direction_to(body.global_position) * Vector3(damage*2, damage*4, damage*2))
if body is CharacterBody3D and body.get("playerallied") == false:
body.deal_damage(damage)
body.global_position.y += 0.2
body.velocity = global_position.direction_to(body.global_position) * Vector3(damage*20, damage*40, damage*20)
$stomparea.scale = Vector3(stomp_radius, stomp_radius, stomp_radius)
$stomparea.monitoring = false
func deal_damage(damage):
print("player takes "+ str(damage)+" damage, leaving "+str(hp)+" HP left")
hp = hp - damage
if hp <= 0:
print("player died! oh no!")
func _on_walljump_timer_timeout():
walljump_ready = true
$walljump_timer.stop()
func _on_aircontrol_timer_timeout():
$aircontrol_timer.stop()
func _on_coyote_timer_timeout():
$coyote_timer.stop()
func _on_dash_timer_timeout():
dash_spent = false
$dash_timer.stop()
pass # Replace with function body.
func _on_gunfire_timer_timeout():
$gunfire_timer.stop()
func _on_gunflash_timer_timeout():
$UserInterface/Gun.texture = tex_pistol_neutral
$gunflash_timer.stop()
func _on_telehand_timer_timeout():
if $UserInterface/Telehand.texture == tex_telehand_pull:
$UserInterface/Telehand.texture = tex_telehand_grip
elif $UserInterface/Telehand.texture == tex_telehand_throw:
$UserInterface/Telehand.visible = false
$telehand_timer.stop()