This repository has been archived on 2022-01-09. You can view files and clone it, but cannot push or open issues/pull-requests.
liblast/Game/Assets/Characters/Player.gd

405 lines
13 KiB
GDScript

extends CharacterBody3D
@export var mouse_sensitivity := 0.15
@onready var main = get_tree().root.get_node("Main")
@onready var gui = main.get_node("GUI")
@onready var settings = gui.settings
@onready var hud = main.get_node("HUD")
@onready var crosshair = hud.get_node("Crosshair")
@onready var vignette = hud.get_node("Vignette")
@onready var banner_busy = preload("res://Assets/Effects/Busy.png")
@onready var banner_chat = preload("res://Assets/Effects/Typing.png")
@onready var head = $Head
@onready var camera = $Head/Camera
@onready var tween = $Head/Camera/Tween
@onready var ground_check = $GroundCheck
@onready var climb_tween = $ClimbTween # undergoing redesign in Godot 4
@onready var climb_check = $ClimbCheck
@onready var body = $Body
@onready var mesh = $Mesh
@onready var weapon = $Head/Camera/Hand/Weapon
var gibs_vfx = preload("res://Assets/Effects/Gibs.tscn")
var blood_decal = preload("res://Assets/Decals/Blood/BloodSplash.tscn")
# climb functions - temporarily disabled
#@onready var body_height = body.shape.height
#@onready var body_y = body.position.y
#@onready var mesh_height = mesh.mesh.mid_height
#@onready var mesh_y = mesh.position.y
#@onready var climb_check_y = climb_check.position.y
@onready var ground_check_y = ground_check.position.y
var input_active = false
var base_fov = 90
var view_zoom_target := 1.0
var view_zoom_direction = true
var view_zoom := view_zoom_target :
set(zoom):
view_zoom = zoom
camera.fov = base_fov / zoom
crosshair.modulate.a = clamp(1 - (zoom - 1), 0, 1)
vignette.modulate.a = (zoom - 1) / 3
var revenge_pid: int #store PID of the player who killed you recently
# climbing code - disabled for now
#var climb_height := 0.75
#var climb_time := 0.15
#var climb_state := 0.0 :
# set(factor):
# #print("climb_state is now ", factor)
# climb_state = factor
# body.shape.height = body_height - factor * climb_height
# body.position.y = body_y + factor * climb_height / 2
#
# mesh.mesh.mid_height = mesh_height - factor * climb_height
# mesh.position.y = mesh_y + factor * climb_height / 2
#
# ground_check.position.y = ground_check_y + factor * climb_height / 2
# climb_check.position.y = climb_check_y + factor * climb_height / 2
var direction := Vector3.ZERO
var accel := 0
var speed := 0
var medium = "ground"
var accel_type := {
"ground": 12,
"air": 1,
"water": 4
}
var speed_type := {
"ground": 10,
"air": 10,
"water": 5
}
var gravity := 28
var jump := 14 * 1
var jetpack_active = false
var jetpack_thrust = 48 # force applied against gravity
var jetpack_tank = 0.5 # maximum fuel (jetpack use time
var jetpack_fuel = jetpack_tank # current fuel (use time) left
var jetpack_recharge = 0.25 # how long to recharge to full
var jetpack_min = 1.0/8
var jetpack_was_active = false
var velocity := Vector3.ZERO
var gravity_vec := Vector3.ZERO
var dead = false: # used to workaround Godot crash when destroying player_nodes
set(value):
match value:
true:
#input_active = false
self.hide()
$Body.disabled = true
#set_physics_process(false)
false:
#input_active = true
self.show()
$Body.disabled = false
$SpawnSFX.play()
$SpawnVFX.emitting = true
#set_physics_process(true)
dead = value
var focus_banner_alpha = 0
var focus_banner_inc = 5
var focus_banner_dec = 1
var focus_banner_show = false
var last_viewed_banner = null
func view_banner(show:bool):
focus_banner_show = show
@rpc(nosync, reliable, auth) func focus_banner(show:bool, type:=0 ):
if show:
$FocusBanner.show()
else:
$FocusBanner.hide()
match type:
0: $FocusBanner.mesh.surface_get_material(0).set("albedo_texture", banner_busy)
1: $FocusBanner.mesh.surface_get_material(0).set("albedo_texture", banner_chat)
@rpc(auth, nosync,unreliable) func update_movement(player_transform, head_rotation, lin_velocity, jetpack):
global_transform = player_transform
head.set_rotation(head_rotation)
jetpack_active = jetpack
linear_velocity = lin_velocity
@rpc(any, sync, reliable) func set_dead(is_dead: bool):
#print("Recieved RPC call for set_dead ", is_dead)
self.dead = is_dead
# if is_multiplayer_authority():
# print("Rebroadcasting RPC call for set_dead ", dead)
# rpc(&'set_dead', dead)
func _ready() -> void:
#Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
view_zoom_target = 1.0
var banner_material = $FocusBanner.mesh.surface_get_material(0).duplicate()
$FocusBanner.mesh.surface_set_material(0, banner_material)
focus_banner(false)
if is_multiplayer_authority(): # prevent puppets from attempting to steer the authority - this just causes RPC errors
input_active = true
$Head/Camera.current = true
else:
input_active = false
$Head/Camera.current = false
$Jetpack/JetpackSound.playing = true
$Jetpack/JetpackSound.stream_paused = true
$SpawnVFX.emitting = true
func aim(event) -> void:
var mouse_motion = event as InputEventMouseMotion
if mouse_motion:
var adjusted_mouse_sensitivity = 1.0
if "Sensitivity" in settings.keys():
adjusted_mouse_sensitivity = mouse_sensitivity * settings["Sensitivity"]
rotation.y -= deg2rad(mouse_motion.relative.x * adjusted_mouse_sensitivity / view_zoom)
var current_tilt: float = head.rotation.x
current_tilt -= deg2rad(mouse_motion.relative.y * adjusted_mouse_sensitivity / view_zoom)
head.rotation.x = clamp(current_tilt, deg2rad(-90), deg2rad(90))
func _input(event) -> void:
if dead:
return
if not input_active:
return
#assert(is_multiplayer_authority() == true, "input_active is true, even though the node is not multiplayer_authority")
if is_multiplayer_authority() == false:
print("Input is active, but we're not the authority. WTF?!")
input_active = false
return
if Input.is_action_just_pressed("view_zoom"):
# tween.remove_all()
# tween.interpolate_property(self, "view_zoom", view_zoom, 4.0, 0.5, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
# tween.start()
view_zoom_direction = true
view_zoom_target = 4.0
if Input.is_action_just_released("view_zoom"):
# tween.remove_all()
# tween.interpolate_property(self, "view_zoom", view_zoom, 1.0, 0.25, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
# tween.start()
view_zoom_direction = false
view_zoom_target = 1.0
aim(event)
var can_shoot = true if view_zoom <= 1.05 else false
if can_shoot and Input.is_action_just_pressed("trigger_primary"):
weapon.trigger(0, true)
elif Input.is_action_just_released("trigger_primary"):
weapon.trigger(0, false)
if can_shoot and Input.is_action_just_pressed("trigger_secondary"):
weapon.trigger(1, true)
elif Input.is_action_just_released("trigger_secondary"):
weapon.trigger(1, false)
func _process(delta):
if dead:
return
$Jetpack/GPUParticles3D.emitting = jetpack_active
if jetpack_active:
$Jetpack/JetpackSound.stream_paused = false
$Jetpack/OmniLight3D.show()
$Jetpack/OmniLight3D.light_energy = randf_range(2, 5)
else:
$Jetpack/JetpackSound.stream_paused = true
$Jetpack/OmniLight3D.hide()
# show focus banner on demand
if focus_banner_show:
focus_banner_alpha = min(focus_banner_alpha + focus_banner_inc * delta, 1.0)
else:
focus_banner_alpha = max(focus_banner_alpha - focus_banner_dec * delta, 0.0)
$FocusBanner.mesh.surface_get_material(0).set("albedo_color", Color(1,1,1, focus_banner_alpha))
#print("PLayer: ", name, "; Focus banner alpha: ", focus_banner_alpha, "; Focus banner show: ", focus_banner_show)
if not input_active:
return
# demand seeing other player's banner:
#print("Last viewed banner is ", last_viewed_banner)
if $"Head/Camera/RayCast3D".is_colliding():
#print("Probe got ", $"Head/Camera/RayCast3D".get_collider().name)
if $"Head/Camera/RayCast3D".get_collider().has_method(&"view_banner"):
$"Head/Camera/RayCast3D".get_collider().view_banner(true)
last_viewed_banner = $"Head/Camera/RayCast3D".get_collider()
elif last_viewed_banner:
last_viewed_banner.view_banner(false)
#assert(is_multiplayer_authority() == true, "input_active is true, even though the node is not multiplayer_authority")
if is_multiplayer_authority() == false:
print("input_active is true, while we're not the authority. WTF?")
input_active = false
return
if view_zoom_direction and view_zoom < view_zoom_target:
view_zoom = min(view_zoom_target, view_zoom + delta * 4)
elif not view_zoom_direction and view_zoom > view_zoom_target:
view_zoom = max(view_zoom_target, view_zoom - delta * 4)
hud.get_node("Stats").get_node("JetpackBar").value = (jetpack_fuel / jetpack_tank) * 100
# weapon spread
weapon.spread = max(lerp(weapon.spread, weapon.spread_min, weapon.spread_lerp), weapon.spread_min)
@rpc(sync, any, reliable) func moan():
var anims = ["01", "02", "03", "04"]
$Pain.play(anims[randi() % 4])
func damage(hp: int):
var target = main.player_list.players[self.get_multiplayer_authority()]
target.health -= hp
@rpc(any, sync, reliable) func die(killer_pid: int):
var gibs = gibs_vfx.instantiate()
get_tree().root.add_child(gibs)
gibs.global_transform = self.global_transform
var decal = blood_decal.instantiate()
get_tree().root.add_child(decal)
decal.global_transform = self.global_transform
#if get_tree().get_rpc_sender_id() != get_multiplayer_authority():
# print ("Death requested by a non-master. Ignoring")
# return
#main.rpc(&'destroy_player', self.get_multiplayer_authority())
main.destroy_player(self.get_multiplayer_authority())
set_dead(true)
#main.chat.rpc(&'chat_notification', "Player [/i][b][color=" + main.player_list.players[self.get_multiplayer_authority()].color.to_html() + "]" + main.player_list.players[self.get_multiplayer_authority()].name + "[/color][/b][i] was killed by " + main.player_list.players[killer_pid].name )
main.chat.chat_notification("Player [/i][b][color=" + main.player_list.players[self.get_multiplayer_authority()].color.to_html() + "]" + main.player_list.players[self.get_multiplayer_authority()].name + "[/color][/b][i] was killed by " + main.player_list.players[killer_pid].name )
revenge_pid = killer_pid
#queue_free()
func update_player(info) -> void:
update_color(info.color)
func update_color(color) -> void: #change player's wolrldmodel color
var player_material = mesh.mesh.surface_get_material(0).duplicate()
player_material.albedo_color = color
mesh.set_surface_override_material(0, player_material)
func _physics_process(delta):
if dead: # workaround for Godot player destruction crash
linear_velocity = Vector3.ZERO
return
if not is_multiplayer_authority():
move_and_slide()
return
direction = Vector3.ZERO
if is_on_floor() and ground_check.is_colliding() and not jetpack_active:
#snap = -get_floor_normal()
medium = "ground"
gravity_vec = Vector3.ZERO
else:
#snap = Vector3.DOWN
medium = "air"
gravity_vec += Vector3.DOWN * gravity * delta
if input_active:
if Input.is_action_just_pressed("move_jump") and is_on_floor():
#snap = Vector3.ZERO
gravity_vec = Vector3.UP * jump
$JumpSFX.rpc(&"play")
if Input.is_action_pressed("move_forward"):
direction -= transform.basis.z
if Input.is_action_pressed("move_backward"):
direction += transform.basis.z
if Input.is_action_pressed("move_left"):
direction -= transform.basis.x
if Input.is_action_pressed("move_right"):
direction += transform.basis.x
jetpack_was_active = jetpack_active
if jetpack_was_active:
jetpack_active = Input.is_action_pressed("move_special") if jetpack_fuel > 0 else false
else:
jetpack_active = Input.is_action_just_pressed("move_special") if jetpack_fuel > jetpack_min else false
if jetpack_active:
gravity_vec[1] += jetpack_thrust * delta
jetpack_fuel -= delta
elif jetpack_fuel < jetpack_tank:
jetpack_fuel = min(jetpack_tank, jetpack_fuel + jetpack_recharge * delta)
#print("Jetpack fuel: ", jetpack_fuel, " active: ", jetpack_active, " delta: ", delta, " gravity vec: ", gravity_vec)
if direction.length() > 0: # normalized() will return a null
direction = direction.normalized()
speed = speed_type[medium]
accel = accel_type[medium]
velocity = velocity.lerp(direction * speed, accel * delta)
linear_velocity = velocity + gravity_vec
move_and_slide()
if not is_on_floor() and not ground_check.is_colliding(): # while in mid-air collisions affect momentum
velocity.x = linear_velocity.x
velocity.z = linear_velocity.z
gravity_vec.y = linear_velocity.y
rpc(&'update_movement', global_transform, head.get_rotation(), linear_velocity, jetpack_active)
# (stair) climbing
# ↓ disabled - Tween is undergoing redesign in Godot 4
# if get_slide_count() > 1 and climb_check.is_colliding() and false:
# #print("climb started at climb state: ", climb_state)
# var test_y = climb_height * (1 - climb_state)
# #print("test_y: ", test_y)
# var climb_test_start = global_transform.translated(Vector3(0, test_y, 0))
# var climb_test_step = Vector3(0,0,-0.1).rotated(Vector3.UP, rotation.y)
# if not test_move(climb_test_start, climb_test_step): # no collision
# var step = climb_check.get_collision_point().y
# var start = global_transform.origin.y
## print("step: ", step, " start: ", start)
# climb_state = clamp((step - start) / climb_height, 0, 1)
# global_transform.origin.y += climb_height * climb_state
# #print("climb state to start: ", climb_state)
## print("Climb height: ", step - start, " Climb state: ", climb_state)
# climb_tween.remove_all()
# climb_tween.interpolate_property(self, "climb_state", climb_state, 0.0, climb_time, Tween.TRANS_CUBIC, Tween.EASE_IN)
# climb_tween.start()