Created
June 6, 2024 19:40
-
-
Save levidavidmurray/6df04f77b07e8d24cf55cabe0c562ad5 to your computer and use it in GitHub Desktop.
Revisions
-
levidavidmurray created this gist
Jun 6, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,608 @@ class_name Player extends FPSController3D signal died signal strength_test_started(fitness: FitnessHandler, strength_test: StrengthTestInteractable) signal key_input(event: InputEventKey) @export var underwater_env: Environment @export var debug = false @export var rb_contact_force := 2.0 @export var scn_audio_listener: PackedScene @export var scn_blood_decal: PackedScene @export var damage_vignette_curve: Curve @export var player_name: String = "[name]": set(value): player_name = value if tps_rig: tps_rig.nameplate.text = player_name @onready var player_input: PlayerInput = $ClientSync @onready var fps_camera_parent: Node3D = %FpsCamera @onready var fps_camera: Camera3D = %FpsCamera/Camera @onready var phantom_fps: PhantomCamera3D = %PhantomFPS @onready var phantom_follow: PhantomCamera3D = %PhantomFollow @onready var camera_shake: Shaker = %CameraShake @onready var fps_rig: FPSRig = %FPSRig @onready var tps_rig: TPSRig = %TPSRig @onready var card_handler: CardHandler = $CardHandler @onready var interact_handler: InteractHandler = $InteractHandler @onready var fitness: FitnessHandler = $FitnessHandler @onready var inventory: Inventory = %Inventory @onready var hotbar: Hotbar = %Hotbar @onready var voip_handler: VOIPHandler = $VOIPHandler @onready var ground_ray = $GroundRay @onready var fog_volume: FogVolume = $FogVolume @onready var cave_particles: GPUParticles3D = $CaveParticles @onready var health: HealthComponent = $HealthComponent @onready var sfx_damage: AudioStreamPlayer3D = %SFX_Damage @onready var sfx_blood: AudioStreamPlayer3D = $SFX_Blood @onready var vignette_rect: ColorRect = %VignetteRect var player_rig: PlayerRig var _handcar_rot_last_frame = INF var _can_move_last_frame = true var _was_on_floor = false var _vignette_orig_outer_radius: float var _vignette_tween: Tween var is_resting = false var can_move = true var can_look = true var head_collision_y_offset: float var p_cam_host: PhantomCameraHost # TODO: Really need an FSM-based player var ladder: Ladder var ladder_enter_pos: Vector3 # TODO: Remove these after Voruk showcase demo var blood_decals: Array[Decal] var last_blood_decal_health: int = 100 var is_holding_voruk = false var is_dancing = false var hold_disable_tween: Tween var player_id := -1: get: return int(str(name)) var is_dead: bool: get: return health.is_dead func _enter_tree(): set_multiplayer_authority(player_id) $HealthComponent.set_multiplayer_authority(1) func _exit_tree(): PlayerManager.remove_player(self.player_id) func _ready(): PlayerManager.add_player(self) mouse_sensitivity = GameSettings.look_sensitivity head_collision_y_offset = (head.position - collision.position).y setup() tps_rig.hide() fps_rig.hide() if is_multiplayer_authority(): _player_ready() else: _player_puppet_ready() interact_handler.activate(camera, inventory, hotbar) player_rig.activate(hotbar) health.died.connect(_on_died) health.damage_taken.connect(_on_damage_taken) func _process(delta): # if can_move and not _can_move_last_frame: # player_rig.play_anim(G.PlayerAnimType.IDLE) _update_spine_rotation() if is_multiplayer_authority(): hotbar.check_input = can_look head.mouse_sensitivity = GameSettings.look_sensitivity head.invert_y_axis = GameSettings.invert_y_axis head.position.y = collision.position.y + head_collision_y_offset if health.is_alive: camera.transform = phantom_fps.transform fps_camera_parent.global_transform = camera.global_transform if ladder and not fly_ability.is_actived(): fly_ability.set_active(true) elif not ladder and fly_ability.is_actived(): fly_ability.set_active(false) # TODO: Remove (or move?) if Input.is_action_just_pressed("voip"): voip_handler.toggle_mute() var carry_weight = inventory.get_weight() fitness.tick(delta, carry_weight, is_moving(), is_sprinting()) if is_resting and velocity.length() > 0.01 and GameManager.can_break_rest: set_resting.rpc(false) if is_dancing and velocity.length() > 0.01: is_dancing = false tps_rig.play_anim(G.PlayerAnimType.IDLE) _can_move_last_frame = can_move func _physics_process(delta): if not can_move: player_input.input_jump = false player_input.input_action_primary = false player_input.input_interact = false player_input.input_toggle_flashlight = false player_input.input_drop_item = false velocity = Vector3() return if ladder: if player_input.input_jump: ladder = null move( delta, player_input.input_dir, player_input.input_jump, player_input.input_crouch, player_input.input_sprint and fitness.can_sprint(), player_input.input_crouch, player_input.input_jump ) if ladder: # constrain player to ladder global_position.x = ladder_enter_pos.x global_position.z = ladder_enter_pos.z var dist_to_top = ladder.global_position.y - global_position.y if dist_to_top < 1.75: global_position = ladder.global_position ladder = null elif is_on_floor() and not _was_on_floor: ladder = null _process_collisions() _handle_handcar_rotation() if player_input.input_action_primary: _handle_action_primary() if player_input.input_interact: interact_handler.interact() if player_input.input_toggle_flashlight: # TODO: Make flashlight an item? player_rig.toggle_flashlight() if player_input.input_drop_item: # TODO: Pass path of node to parent item to var result = _head_raycast() var parent_path = NodePath("") if result and result.collider: parent_path = result.collider.get_path() hotbar.drop_current_item(fps_rig.item_hand_pos.global_position, parent_path) player_input.input_jump = false player_input.input_action_primary = false player_input.input_interact = false player_input.input_toggle_flashlight = false player_input.input_drop_item = false tps_rig.velocity = velocity tps_rig.is_crouching = is_crouching() _was_on_floor = is_on_floor() func _unhandled_key_input(event: InputEvent): if not is_multiplayer_authority(): return if event is InputEventKey and event.is_released(): event = event as InputEventKey if event.keycode == KEY_1: phantom_fps.global_position.y -= 0.25 # dance.rpc(G.PlayerAnimType.DANCE_CLASSIC) if event.keycode == KEY_2: phantom_fps.global_position.y += 0.25 dance.rpc(G.PlayerAnimType.DANCE_LUDDY) if event.keycode == KEY_3: var alive_players = PlayerManager.get_alive_players() if alive_players.size() > 0: var player = alive_players[0] tps_rig.drag_bone_target = player.tps_rig.right_hand else: # TODO: Remove after showcase key_input.emit(event) @rpc func dance(anim_type: G.PlayerAnimType): is_dancing = true tps_rig.play_anim(anim_type) # TODO: Remove after showcase? @rpc func hold_voruk(): is_holding_voruk = not is_holding_voruk if hold_disable_tween and hold_disable_tween.is_running(): hold_disable_tween.kill() hold_disable_tween = create_tween() if is_holding_voruk: hold_disable_tween.tween_method(tps_rig._set_body_blend, 0.0, 1.0, 0.25) tps_rig._upper_body_sm.travel("Hold_Voruk") else: hold_disable_tween = create_tween() hold_disable_tween.tween_method(tps_rig._set_body_blend, 1.0, 0.0, 0.4) tps_rig._upper_body_sm.travel("Idle") func _input(event: InputEvent) -> void: if not is_multiplayer_authority(): return if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED: return if not can_look: return # Mouse look (only if the mouse is captured). if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: rotate_head(event.relative) @rpc("call_local", "reliable") func go_fetal(): if is_multiplayer_authority(): _drop_inventory() can_move = false can_look = false tps_rig.anim_tree.active = true tps_rig.skeleton.show_rest_only = false tps_rig.play_anim(G.PlayerAnimType.FETAL) fps_rig.play_anim(G.PlayerAnimType.FETAL) @rpc("call_local") func set_player_name(_name: String): player_name = _name @rpc("call_local", "reliable") func set_resting(resting: bool): is_resting = resting can_look = not resting if is_resting: GameManager.player_rested() @rpc("any_peer", "call_local") func set_player_position(pos: Vector3): global_position = pos @rpc("any_peer", "call_local") func set_player_rotation(rot: Vector3, is_global = false): if is_global: global_rotation = rot else: rotation = rot head.actual_rotation.y = rotation.y @rpc("call_local", "reliable") func take_damage(damage: int): health.take_damage(damage) @rpc("call_local", "reliable") func die(): take_damage(health.health) @rpc("any_peer", "call_local", "reliable") func respawn(): GameManager.stop_spectating() global_position = GameManager.player_spawn_pos health.reset() tps_rig.set_ragdoll(false) voip_handler.set_mute(false) fps_rig.visible = is_multiplayer_authority() tps_rig.visible = not is_multiplayer_authority() tps_rig.nameplate.visible = not is_multiplayer_authority() if not is_multiplayer_authority(): tps_rig._set_body_blend(0.0) can_look = true can_move = true if p_cam_host: p_cam_host.queue_free() p_cam_host = null func start_spectate() -> PhantomCamera3D: reset_vignette() fps_rig.hide() tps_rig.show() tps_rig.set_ragdoll(true) tps_rig.nameplate.hide() return phantom_follow.duplicate() as PhantomCamera3D @rpc("call_local", "reliable") func _on_died(): if is_multiplayer_authority(): _drop_inventory() p_cam_host = PhantomCameraHost.new() camera.add_child(p_cam_host) else: tps_rig.set_ragdoll(true) tps_rig.nameplate.hide() died.emit() voip_handler.set_mute(true) if fps_rig.flashlight.visible: fps_rig.toggle_flashlight() tps_rig.toggle_flashlight() can_look = false can_move = false func shake_camera(stress: float): camera_shake.shake(stress) func is_moving() -> bool: return Vector2(velocity.x, velocity.z).length() > 0.1 and is_on_floor() func is_sprinting() -> bool: return ( player_input.input_sprint and Vector2(velocity.x, velocity.z).length() > _normal_speed and is_on_floor() ) func set_disabled(disabled: bool): can_move = not disabled can_look = not disabled func vertical_look_percent() -> float: return head.rotation_degrees.x / vertical_angle_limit # Cave FX functions should be moved func show_cave_fx(): cave_particles.emitting = true cave_particles.show() # fog_volume.show() func hide_cave_fx(): cave_particles.emitting = false cave_particles.hide() fog_volume.hide() func _player_ready(): GameManager.player = self player_name = GameManager.steam_name hotbar.slot_count = inventory.slot_count fps_rig.show() player_rig = fps_rig Input.mouse_mode = Input.MOUSE_MODE_CAPTURED emerged.connect(_on_controller_emerged.bind()) submerged.connect(_on_controller_submerged.bind()) set_player_name.rpc(player_name) fog_volume.show() voip_handler.init_capture() interact_handler.strength_test_interacted.connect(_on_strength_test_interacted) interact_handler.rest_interacted.connect(_on_rest_interacted) _vignette_orig_outer_radius = vignette_rect.material.get("shader_parameter/outer_radius") var audio_listener = scn_audio_listener.instantiate() as Node3D camera.add_child(audio_listener) var studio_listener = audio_listener.get_node("StudioListener3D") as StudioListener3D studio_listener.rigidbody = self phantom_fps.set_priority(1) camera.make_current() fps_camera.make_current() func _player_puppet_ready(): player_rig = tps_rig fps_rig.hide() tps_rig.show() camera.clear_current() fps_camera.clear_current() fog_volume.queue_free() voip_handler.init_playback() func _update_spine_rotation(): tps_rig.set_spine_blend_space(vertical_look_percent()) func _process_collisions(): for i in range(get_slide_collision_count()): var col = get_slide_collision(i) for k in range(col.get_collision_count()): var body = col.get_collider(k) if body == null: continue if body is RigidBody3D: _collide_rigidbody(body, col, k) elif body is Ladder: _collide_ladder(body, col, k) func _collide_ladder(body: Ladder, col: KinematicCollision3D, col_idx: int): if ladder: return print("collide ladder, vlp: %s" % vertical_look_percent()) if is_on_floor() and vertical_look_percent() > 0.55: ladder = body ladder_enter_pos = global_position func _collide_rigidbody(body: RigidBody3D, col: KinematicCollision3D, col_idx: int): var point = col.get_position(col_idx) - body.global_position body.apply_impulse(-col.get_normal(col_idx) * rb_contact_force, point) func _handle_action_primary(): player_rig.primary_action(self) # var item = hotbar.get_selected_item() # if item is CardItemData and card_handler.can_throw(): # player_rig.play_anim(G.PlayerAnimType.CARD_THROW) # card_handler.use_card(item) # TODO: Commented out for testing # inventory_handler.drop_from_inventory(hotbar.selection_index) pass func _handle_handcar_rotation(): var body = ground_ray.get_collider() if body is Handcar: var handcar = body as Handcar var rot = handcar.rotation_degrees if rot.y != _handcar_rot_last_frame and _handcar_rot_last_frame != INF: var diff = rot.y - _handcar_rot_last_frame rotate_y(deg_to_rad(diff)) head.actual_rotation.y = rotation.y _handcar_rot_last_frame = rot.y func _head_raycast(dir = Vector3.DOWN) -> Dictionary: var space_state = get_world_3d().direct_space_state var origin = head.global_position var end = origin + dir * 100 var query = PhysicsRayQueryParameters3D.create(origin, end, 1 << 0) return space_state.intersect_ray(query) func _spawn_blood_decal(): # slightly randomize dir in x and z var dir = Vector3.DOWN dir.x += GameManager.rng.randf_range(-0.1, 0.1) dir.z += GameManager.rng.randf_range(-0.1, 0.1) var result = _head_raycast(dir) if not result: return await G.wait(0.4) sfx_blood.play() var decal = scn_blood_decal.instantiate() as Decal GameManager.world.add_child(decal) decal.global_position = result.position decal.global_transform = G.align_with_normal(decal.global_transform, result.normal) decal.global_position = global_position # random rotation decal.rotation_degrees.y = GameManager.rng.randf_range(0, 360) func _drop_inventory(): var result = _head_raycast() var parent_path = NodePath("") var positions = [] var parent_paths = [] positions.resize(inventory.slot_count) parent_paths.resize(inventory.slot_count) if result and result.collider: parent_path = result.collider.get_path() for i in range(inventory.slot_count): positions[i] = fps_rig.item_hand_pos.global_position parent_paths[i] = parent_path inventory.drop_inventory.rpc(positions, parent_paths) func reset_vignette(): if _vignette_tween: _vignette_tween.kill() var vignette_mat = vignette_rect.material as ShaderMaterial vignette_mat.set("shader_parameter/outer_radius", _vignette_orig_outer_radius) vignette_mat.set("shader_parameter/alpha", 0.0) func _handle_damage_vignette(): if _vignette_tween: _vignette_tween.kill() _vignette_tween = create_tween().set_parallel() var vignette_mat = vignette_rect.material as ShaderMaterial var outer_radius = vignette_mat.get("shader_parameter/outer_radius") _vignette_tween.tween_property(vignette_mat, "shader_parameter/alpha", 1.0, 0.25) _vignette_tween.tween_property( vignette_mat, "shader_parameter/outer_radius", outer_radius - 1.0, 0.25 ) _vignette_tween.chain() _vignette_tween.tween_property(vignette_mat, "shader_parameter/alpha", 0.0, 0.5) _vignette_tween.tween_property( vignette_mat, "shader_parameter/outer_radius", _vignette_orig_outer_radius, 1.0 ) func _on_damage_taken(damage: int): shake_camera(0.4) if is_multiplayer_authority(): _handle_damage_vignette() sfx_damage.play() var blood_decal_health_interval = 20 # every 20% health drop, instantiate a blood decal # example edge case: if health is at 84 and drops to 75, a decal should still be instantiate if last_blood_decal_health - health.health >= blood_decal_health_interval: _spawn_blood_decal() last_blood_decal_health -= blood_decal_health_interval func _on_strength_test_interacted(strength_test: StrengthTestInteractable): can_move = false can_look = false strength_test_started.emit(fitness, strength_test) func _on_rest_interacted(): set_resting.rpc(true) func _on_controller_emerged(): if not is_multiplayer_authority(): return camera.environment = null func _on_controller_submerged(): if not is_multiplayer_authority(): return camera.environment = underwater_env func _on_hb_punch_body_entered(_body: Node3D): if not is_multiplayer_authority(): return if not $SFX_PunchImpact.playing: $SFX_PunchImpact.play() # TODO: Remember why top level player node is not multiplayer authority # func _is_multiplayer_authority() -> bool: # return player_id == multiplayer.get_unique_id() or debug func is_fly_mode() -> bool: return fly_ability.is_actived() or ladder