200 lines
6.4 KiB
GDScript
200 lines
6.4 KiB
GDScript
class_name Player extends CharacterBody3D
|
|
|
|
const SPEED = 5.0
|
|
const JUMP_VELOCITY = 4.5
|
|
|
|
@export var _projectile_holder: Node
|
|
@export var _projectile_point: Marker3D
|
|
@export var _camera_pivot: Node3D
|
|
@export var _camera: Camera3D
|
|
@export var _line_of_sight: ShapeCast3D
|
|
@export var _shot_sound: AudioStreamPlayer3D
|
|
|
|
@export var id: int
|
|
@export var input_dir := Vector2()
|
|
@export var input_jumped := false
|
|
|
|
var _max_speed := 16
|
|
var _mouse_sensitivity := 0.008 # radians/pixel
|
|
|
|
var _projectile_speed := 12.0
|
|
## Could be only one thing at a time to consider.
|
|
var _interaction_selection: Node3D
|
|
|
|
var controls_disabled := false
|
|
@export var held_thing := { "item_id": &"empty_hand", "count": 0 }
|
|
|
|
|
|
# What the others see.
|
|
func _init_bystander() -> void:
|
|
$Nickname.text = GameState.fetch().player_data[id].username
|
|
$Nickname.show()
|
|
|
|
|
|
func _ready() -> void:
|
|
if id != multiplayer.get_unique_id():
|
|
_init_bystander()
|
|
return
|
|
|
|
$Model.hide()
|
|
_camera.make_current()
|
|
|
|
# TODO: reliable rng
|
|
var spawn_point: Marker3D = get_tree().get_nodes_in_group("spawn_points").pick_random()
|
|
global_position = spawn_point.global_position
|
|
reset_physics_interpolation()
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
if id != multiplayer.get_unique_id():
|
|
return
|
|
|
|
_process_input()
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
## Process interactivity selection.
|
|
if id == multiplayer.get_unique_id():
|
|
var collider: Object = null
|
|
if _line_of_sight.is_colliding():
|
|
var possible_collider = _line_of_sight.get_collider(0)
|
|
if empty_handed(): collider = possible_collider
|
|
elif possible_collider and \
|
|
GameState.are_bundles_stackable(held_thing, possible_collider.owner.item_bundle):
|
|
collider = possible_collider
|
|
if collider != _interaction_selection:
|
|
if _interaction_selection != null:
|
|
_interaction_selection.get_parent().mark_non_interactive()
|
|
if collider != null:
|
|
collider.owner.mark_interactive()
|
|
_interaction_selection = collider
|
|
|
|
# Add the gravity.
|
|
if not is_on_floor():
|
|
velocity += get_gravity() * delta
|
|
|
|
# Handle jump.
|
|
if input_jumped and is_on_floor():
|
|
velocity.y = JUMP_VELOCITY
|
|
|
|
# Get the input direction and handle the movement/deceleration.
|
|
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
|
if direction:
|
|
velocity.x = direction.x * SPEED
|
|
velocity.z = direction.z * SPEED
|
|
else:
|
|
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
velocity.z = move_toward(velocity.z, 0, SPEED)
|
|
|
|
if velocity.length() > _max_speed:
|
|
velocity = velocity.clampf(0, _max_speed)
|
|
|
|
$StairStepper.stair_step_up(direction)
|
|
|
|
if move_and_slide() and is_on_floor():
|
|
for idx in range(get_slide_collision_count()):
|
|
var collision := get_slide_collision(idx)
|
|
if collision.get_collider().is_in_group("voids"):
|
|
# TODO: reliable rng
|
|
var spawn_point: Marker3D = get_tree().get_nodes_in_group("spawn_points").pick_random()
|
|
global_position = spawn_point.global_position
|
|
reset_physics_interpolation()
|
|
|
|
$StairStepper.stair_step_down()
|
|
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func hold_thing(p_bundle: Dictionary) -> void:
|
|
var item := GameState.fetch().INVENTORY_ITEM_DB[p_bundle["item_id"]] as InventoryItem
|
|
held_thing = p_bundle
|
|
var base_node := _camera_pivot.get_node("HeldViewmodel")
|
|
for child in base_node.get_children():
|
|
child.queue_free()
|
|
if item.model != null:
|
|
var model = item.model.instantiate()
|
|
base_node.add_child(model)
|
|
else:
|
|
# Create a icon sprite based one instead.
|
|
var model := preload("res://src/ingame/quad_viewmodel.tscn").instantiate()
|
|
model.reflect_bundle(p_bundle)
|
|
base_node.add_child(model)
|
|
|
|
if item.model != null and id == multiplayer.get_unique_id():
|
|
# Disable depth test and increase render priority.
|
|
# TODO: in more complex scenarios model scene might want to have its own callback for this.
|
|
for model in base_node.get_children():
|
|
for submodel in model.get_children():
|
|
var mesh := submodel.mesh.duplicate() as Mesh
|
|
var material := mesh.surface_get_material(0).duplicate()
|
|
material.render_priority += 1
|
|
material.no_depth_test = true
|
|
mesh.surface_set_material(0, material)
|
|
submodel.mesh = mesh
|
|
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func throw_thing() -> void:
|
|
held_thing = { "item_id": &"empty_hand", "count": 0 }
|
|
for child in _camera_pivot.get_node("HeldViewmodel").get_children():
|
|
child.queue_free()
|
|
|
|
|
|
func empty_handed() -> bool:
|
|
return held_thing["item_id"] == &"empty_hand"
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if controls_disabled or id != multiplayer.get_unique_id():
|
|
return
|
|
|
|
if event is InputEventMouseMotion:
|
|
rotate_y(-event.relative.x * _mouse_sensitivity)
|
|
_camera_pivot.rotate_x(-event.relative.y * _mouse_sensitivity)
|
|
_camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -1.2, 1.2)
|
|
return
|
|
|
|
if event.is_action_pressed("pick"):
|
|
if _interaction_selection != null:
|
|
if empty_handed():
|
|
_interaction_selection.owner.get_picked_up.rpc()
|
|
hold_thing.rpc(_interaction_selection.owner.item_bundle)
|
|
elif GameState.are_bundles_stackable(held_thing, _interaction_selection.owner.item_bundle):
|
|
_interaction_selection.owner.get_picked_up.rpc()
|
|
var bundle := GameState.combine_bundles(held_thing, _interaction_selection.owner.item_bundle)
|
|
hold_thing.rpc(bundle)
|
|
|
|
if event.is_action_pressed("fire") and not empty_handed():
|
|
var item := GameState.fetch().INVENTORY_ITEM_DB[held_thing["item_id"]] as InventoryItem
|
|
var new_projectile: Node3D = item.bomb.instantiate()
|
|
_projectile_holder.add_child(new_projectile, true)
|
|
_set_projectile_authority.rpc(new_projectile.get_path(), id)
|
|
new_projectile.set_item_bundle.rpc(held_thing)
|
|
new_projectile.set_global_pos.rpc(_projectile_point.global_position)
|
|
new_projectile.rotation = rotation
|
|
new_projectile.sender_body = self
|
|
new_projectile.sender_id = id
|
|
|
|
new_projectile.body.process_mode = Node.PROCESS_MODE_INHERIT
|
|
new_projectile.body.linear_velocity = -_camera.global_basis.z * _projectile_speed
|
|
|
|
_shot_sound.play()
|
|
throw_thing.rpc()
|
|
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func _set_projectile_authority(path: NodePath, authority_id: int) -> void:
|
|
if not _projectile_holder.has_node(path):
|
|
return
|
|
|
|
_projectile_holder.get_node(path).set_multiplayer_authority(authority_id)
|
|
|
|
|
|
func _process_input() -> void:
|
|
if controls_disabled:
|
|
input_dir = Vector2.ZERO
|
|
input_jumped = false
|
|
return
|
|
|
|
input_dir = Input.get_vector("strafe_left", "strafe_right", "move_forward", "move_backward")
|
|
input_jumped = Input.is_action_just_pressed("jump")
|