Merge pull request 'Feat/12 add effects and better animations to fpv' (#17) from feat/12-add-effects-and-better-animations-to-fpv into main
Reviewed-on: #17
This commit was merged in pull request #17.
This commit is contained in:
@@ -14,6 +14,7 @@ var _target_tilt: float = 0.0
|
|||||||
var _current_tilt: float = 0.0
|
var _current_tilt: float = 0.0
|
||||||
var _target_fov: float = 90.0
|
var _target_fov: float = 90.0
|
||||||
var _weapon_fov_override: float = -1.0
|
var _weapon_fov_override: float = -1.0
|
||||||
|
var _step_offset_y: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -80,11 +81,14 @@ func _process(delta: float) -> void:
|
|||||||
camera.position.y = lerp(camera.position.y, 0.0, 0.15)
|
camera.position.y = lerp(camera.position.y, 0.0, 0.15)
|
||||||
camera.position.x = lerp(camera.position.x, 0.0, 0.15)
|
camera.position.x = lerp(camera.position.x, 0.0, 0.15)
|
||||||
|
|
||||||
|
# ── Step Smoothing ─────────────────────────────────────────────────────
|
||||||
|
_step_offset_y = lerpf(_step_offset_y, 0.0, 15.0 * delta)
|
||||||
|
camera.position.y += _step_offset_y
|
||||||
|
|
||||||
# ── Wall-run tilt ─────────────────────────────────────────────────────
|
# ── Wall-run tilt ─────────────────────────────────────────────────────
|
||||||
_current_tilt = lerpf(_current_tilt, _target_tilt, 1.0 - exp(-params.wall_run_tilt_speed * delta))
|
_current_tilt = lerpf(_current_tilt, _target_tilt, 1.0 - exp(-params.wall_run_tilt_speed * delta))
|
||||||
camera.rotation.z = deg_to_rad(_current_tilt)
|
camera.rotation.z = deg_to_rad(_current_tilt)
|
||||||
|
|
||||||
|
|
||||||
## Called by the movement system to set wall-run tilt direction.
|
## Called by the movement system to set wall-run tilt direction.
|
||||||
## side: -1.0 (left wall), 1.0 (right wall), 0.0 (no tilt)
|
## side: -1.0 (left wall), 1.0 (right wall), 0.0 (no tilt)
|
||||||
func set_wall_tilt(side: float) -> void:
|
func set_wall_tilt(side: float) -> void:
|
||||||
@@ -100,3 +104,9 @@ func set_weapon_fov(fov: float) -> void:
|
|||||||
## Called to reset tilt (e.g., on landing).
|
## Called to reset tilt (e.g., on landing).
|
||||||
func clear_wall_tilt() -> void:
|
func clear_wall_tilt() -> void:
|
||||||
_target_tilt = 0.0
|
_target_tilt = 0.0
|
||||||
|
|
||||||
|
## Adds an offset to the camera so that when the player physics body snaps up a step,
|
||||||
|
## the camera interpolates smoothly instead of snapping.
|
||||||
|
func add_step_offset(offset_y: float) -> void:
|
||||||
|
_step_offset_y += offset_y
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ var _dash_icon: TextureRect
|
|||||||
var _grapple_label: Label
|
var _grapple_label: Label
|
||||||
var _dash_label: Label
|
var _dash_label: Label
|
||||||
var _fps_label: Label
|
var _fps_label: Label
|
||||||
|
var _standalone_speed_label: Label
|
||||||
var _player: CharacterBody3D
|
var _player: CharacterBody3D
|
||||||
var _debug_ui_panel: PanelContainer
|
var _debug_ui_panel: PanelContainer
|
||||||
|
|
||||||
@@ -489,6 +490,17 @@ func _build_hud() -> void:
|
|||||||
_fps_label.position = Vector2(12, 12)
|
_fps_label.position = Vector2(12, 12)
|
||||||
canvas.add_child(_fps_label)
|
canvas.add_child(_fps_label)
|
||||||
|
|
||||||
|
_standalone_speed_label = Label.new()
|
||||||
|
_standalone_speed_label.name = "StandaloneSpeedLabel"
|
||||||
|
_standalone_speed_label.text = "Speed: 0.0 m/s"
|
||||||
|
_standalone_speed_label.add_theme_font_size_override("font_size", 24)
|
||||||
|
_standalone_speed_label.add_theme_color_override("font_color", Color(0.3, 1.0, 0.5))
|
||||||
|
_standalone_speed_label.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
||||||
|
_standalone_speed_label.add_theme_constant_override("outline_size", 4)
|
||||||
|
_standalone_speed_label.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_standalone_speed_label.position = Vector2(12, 45)
|
||||||
|
canvas.add_child(_standalone_speed_label)
|
||||||
|
|
||||||
_debug_ui_panel = PanelContainer.new()
|
_debug_ui_panel = PanelContainer.new()
|
||||||
_debug_ui_panel.name = "InfoPanel"
|
_debug_ui_panel.name = "InfoPanel"
|
||||||
_debug_ui_panel.offset_left = 12
|
_debug_ui_panel.offset_left = 12
|
||||||
@@ -664,6 +676,17 @@ func _process(_delta: float) -> void:
|
|||||||
if _speed_label:
|
if _speed_label:
|
||||||
_speed_label.text = "Speed: %.1f m/s (total: %.1f)" % [hspeed, total_speed]
|
_speed_label.text = "Speed: %.1f m/s (total: %.1f)" % [hspeed, total_speed]
|
||||||
|
|
||||||
|
if _standalone_speed_label:
|
||||||
|
if SettingsManager.show_movement_speed and not SettingsManager.show_debug_ui:
|
||||||
|
_standalone_speed_label.visible = true
|
||||||
|
_standalone_speed_label.text = "Speed: %.1f m/s" % hspeed
|
||||||
|
if _fps_label and _fps_label.visible:
|
||||||
|
_standalone_speed_label.position = Vector2(12, 45)
|
||||||
|
else:
|
||||||
|
_standalone_speed_label.position = Vector2(12, 12)
|
||||||
|
else:
|
||||||
|
_standalone_speed_label.visible = false
|
||||||
|
|
||||||
if _state_label:
|
if _state_label:
|
||||||
var sm = _player.get_node_or_null("MovementStateMachine")
|
var sm = _player.get_node_or_null("MovementStateMachine")
|
||||||
if sm:
|
if sm:
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var default_bindings = {
|
|||||||
|
|
||||||
var current_bindings = {}
|
var current_bindings = {}
|
||||||
var show_debug_ui: bool = false
|
var show_debug_ui: bool = false
|
||||||
|
var show_movement_speed: bool = false
|
||||||
var username: String = "Player"
|
var username: String = "Player"
|
||||||
var mouse_sensitivity: float = 0.002
|
var mouse_sensitivity: float = 0.002
|
||||||
var ads_sensitivity: float = 0.001
|
var ads_sensitivity: float = 0.001
|
||||||
@@ -134,6 +135,7 @@ func _save_settings() -> void:
|
|||||||
var save_data = {
|
var save_data = {
|
||||||
"bindings": current_bindings,
|
"bindings": current_bindings,
|
||||||
"show_debug_ui": show_debug_ui,
|
"show_debug_ui": show_debug_ui,
|
||||||
|
"show_movement_speed": show_movement_speed,
|
||||||
"username": username,
|
"username": username,
|
||||||
"mouse_sensitivity": mouse_sensitivity,
|
"mouse_sensitivity": mouse_sensitivity,
|
||||||
"ads_sensitivity": ads_sensitivity,
|
"ads_sensitivity": ads_sensitivity,
|
||||||
@@ -172,6 +174,7 @@ func _load_settings() -> void:
|
|||||||
current_bindings[action] = loaded_bindings[action]
|
current_bindings[action] = loaded_bindings[action]
|
||||||
if data.has("username"): username = data["username"]
|
if data.has("username"): username = data["username"]
|
||||||
if data.has("show_debug_ui"): show_debug_ui = data["show_debug_ui"]
|
if data.has("show_debug_ui"): show_debug_ui = data["show_debug_ui"]
|
||||||
|
if data.has("show_movement_speed"): show_movement_speed = data["show_movement_speed"]
|
||||||
if data.has("mouse_sensitivity"): mouse_sensitivity = data["mouse_sensitivity"]
|
if data.has("mouse_sensitivity"): mouse_sensitivity = data["mouse_sensitivity"]
|
||||||
if data.has("ads_sensitivity"): ads_sensitivity = data["ads_sensitivity"]
|
if data.has("ads_sensitivity"): ads_sensitivity = data["ads_sensitivity"]
|
||||||
if data.has("world_fov"): world_fov = data["world_fov"]
|
if data.has("world_fov"): world_fov = data["world_fov"]
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ class_name MovementParams
|
|||||||
@export var wall_angle_threshold: float = 70.0
|
@export var wall_angle_threshold: float = 70.0
|
||||||
@export var wall_detect_distance: float = 0.7
|
@export var wall_detect_distance: float = 0.7
|
||||||
@export var wall_ray_up_height: float = 0.6
|
@export var wall_ray_up_height: float = 0.6
|
||||||
@export var wall_ray_down_height: float = -0.3
|
@export var wall_ray_down_height: float = 0.0
|
||||||
|
|
||||||
# ── Wall Climb ────────────────────────────────────────────────────────────────
|
# ── Wall Climb ────────────────────────────────────────────────────────────────
|
||||||
@export var wall_climb_speed: float = 6.0
|
@export var wall_climb_speed: float = 6.0
|
||||||
@export var wall_climb_duration: float = 3.0
|
@export var wall_climb_duration: float = 3.0
|
||||||
@export var wall_climb_max_angle: float = 55.0
|
@export var wall_climb_max_angle: float = 75.0
|
||||||
@export var wall_climb_vault_forward: float = 16.0
|
@export var wall_climb_vault_forward: float = 16.0
|
||||||
@export var wall_climb_vault_up: float = 14.0
|
@export var wall_climb_vault_up: float = 14.0
|
||||||
@export var wall_climb_vault_height_check: float = 1.0
|
@export var wall_climb_vault_height_check: float = 1.0
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ var original_capsule_height: float = 1.8
|
|||||||
var slide_cooldown_timer: float = 0.0 # Prevents instant slide re-entry
|
var slide_cooldown_timer: float = 0.0 # Prevents instant slide re-entry
|
||||||
var wall_cooldown_timer: float = 0.0 # Prevents instant re-attachment after wall jump
|
var wall_cooldown_timer: float = 0.0 # Prevents instant re-attachment after wall jump
|
||||||
var is_crouched: bool = false
|
var is_crouched: bool = false
|
||||||
|
var can_wall_climb: bool = true
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -221,7 +222,7 @@ func detect_wall_horizontal() -> Vector3:
|
|||||||
var hit := space_state.intersect_ray(ray)
|
var hit := space_state.intersect_ray(ray)
|
||||||
if not hit.is_empty():
|
if not hit.is_empty():
|
||||||
var collider = hit.get("collider")
|
var collider = hit.get("collider")
|
||||||
if collider is PlayerMovementController:
|
if collider is PlayerMovementController or collider is CharacterBody3D or collider.has_method("take_damage"):
|
||||||
continue
|
continue
|
||||||
var n: Vector3 = hit.get("normal", Vector3.ZERO)
|
var n: Vector3 = hit.get("normal", Vector3.ZERO)
|
||||||
# Wall must be roughly vertical (normal mostly horizontal)
|
# Wall must be roughly vertical (normal mostly horizontal)
|
||||||
@@ -242,7 +243,7 @@ func detect_wall_horizontal() -> Vector3:
|
|||||||
var hit := space_state.intersect_ray(ray)
|
var hit := space_state.intersect_ray(ray)
|
||||||
if not hit.is_empty():
|
if not hit.is_empty():
|
||||||
var collider = hit.get("collider")
|
var collider = hit.get("collider")
|
||||||
if collider is PlayerMovementController:
|
if collider is PlayerMovementController or collider is CharacterBody3D or collider.has_method("take_damage"):
|
||||||
continue
|
continue
|
||||||
var n: Vector3 = hit.get("normal", Vector3.ZERO)
|
var n: Vector3 = hit.get("normal", Vector3.ZERO)
|
||||||
if absf(n.y) < 0.3 and n.length_squared() > 0.0:
|
if absf(n.y) < 0.3 and n.length_squared() > 0.0:
|
||||||
@@ -260,21 +261,15 @@ func detect_wall_forward() -> Dictionary:
|
|||||||
if not player:
|
if not player:
|
||||||
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
|
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
|
||||||
|
|
||||||
var hvel := Vector3(player.velocity.x, 0.0, player.velocity.z)
|
var move_dir := -player.global_transform.basis.z
|
||||||
var move_dir := Vector3.ZERO
|
|
||||||
if hvel.length_squared() > 0.1:
|
|
||||||
move_dir = hvel.normalized()
|
|
||||||
else:
|
|
||||||
move_dir = -player.global_transform.basis.z
|
|
||||||
move_dir.y = 0.0
|
move_dir.y = 0.0
|
||||||
if move_dir.length_squared() > 0.01:
|
if move_dir.length_squared() > 0.01:
|
||||||
move_dir = move_dir.normalized()
|
move_dir = move_dir.normalized()
|
||||||
|
else:
|
||||||
if move_dir.length_squared() < 0.1:
|
|
||||||
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
|
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
|
||||||
|
|
||||||
var space_state := player.get_world_3d().direct_space_state
|
var space_state := player.get_world_3d().direct_space_state
|
||||||
var dist := params.wall_detect_distance
|
var dist := 1.2 # Increased to reliably detect walls at an angle
|
||||||
|
|
||||||
# Lower ray (feet/knees)
|
# Lower ray (feet/knees)
|
||||||
var origin_low := player.global_position + Vector3.UP * params.wall_ray_down_height
|
var origin_low := player.global_position + Vector3.UP * params.wall_ray_down_height
|
||||||
@@ -294,8 +289,8 @@ func detect_wall_forward() -> Dictionary:
|
|||||||
|
|
||||||
# Get the normal from the highest point we hit, or the lowest if we only hit low
|
# Get the normal from the highest point we hit, or the lowest if we only hit low
|
||||||
var best_hit = hit_mid if not hit_mid.is_empty() else hit_low
|
var best_hit = hit_mid if not hit_mid.is_empty() else hit_low
|
||||||
|
var collider = best_hit.get("collider")
|
||||||
if best_hit.get("collider") is PlayerMovementController:
|
if collider is PlayerMovementController or collider is CharacterBody3D or collider.has_method("take_damage"):
|
||||||
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
|
return {"hit": false, "normal": Vector3.ZERO, "is_short": false}
|
||||||
|
|
||||||
var normal: Vector3 = best_hit.get("normal", Vector3.ZERO)
|
var normal: Vector3 = best_hit.get("normal", Vector3.ZERO)
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ var grapple_swing_player: AudioStreamPlayer
|
|||||||
# UI
|
# UI
|
||||||
var hit_marker: Control
|
var hit_marker: Control
|
||||||
var _hit_marker_tween: Tween
|
var _hit_marker_tween: Tween
|
||||||
|
var reload_ring: Control
|
||||||
|
|
||||||
|
var grenades: int = 2
|
||||||
|
var is_holding_grenade: bool = false
|
||||||
|
var trajectory_mesh: MultiMeshInstance3D
|
||||||
|
|
||||||
# Grapple
|
# Grapple
|
||||||
var grapple_rope: MeshInstance3D
|
var grapple_rope: MeshInstance3D
|
||||||
@@ -96,6 +101,7 @@ func _ready() -> void:
|
|||||||
|
|
||||||
_setup_audio()
|
_setup_audio()
|
||||||
_setup_grapple()
|
_setup_grapple()
|
||||||
|
_setup_grenade_trajectory()
|
||||||
|
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
_damage_layer = CanvasLayer.new()
|
_damage_layer = CanvasLayer.new()
|
||||||
@@ -214,6 +220,11 @@ func _setup_hit_marker() -> void:
|
|||||||
rect.rotation = angle
|
rect.rotation = angle
|
||||||
hit_marker.add_child(rect)
|
hit_marker.add_child(rect)
|
||||||
|
|
||||||
|
# Reload Ring
|
||||||
|
reload_ring = load("res://ui/reload_ring.gd").new()
|
||||||
|
reload_ring.set_anchors_preset(Control.PRESET_CENTER)
|
||||||
|
_damage_layer.add_child(reload_ring)
|
||||||
|
|
||||||
func _setup_grapple() -> void:
|
func _setup_grapple() -> void:
|
||||||
grapple_rope = MeshInstance3D.new()
|
grapple_rope = MeshInstance3D.new()
|
||||||
var rope_mesh = CylinderMesh.new()
|
var rope_mesh = CylinderMesh.new()
|
||||||
@@ -230,6 +241,24 @@ func _setup_grapple() -> void:
|
|||||||
grapple_rope.visible = false
|
grapple_rope.visible = false
|
||||||
get_tree().current_scene.call_deferred("add_child", grapple_rope)
|
get_tree().current_scene.call_deferred("add_child", grapple_rope)
|
||||||
|
|
||||||
|
func _setup_grenade_trajectory() -> void:
|
||||||
|
trajectory_mesh = MultiMeshInstance3D.new()
|
||||||
|
var mm = MultiMesh.new()
|
||||||
|
mm.transform_format = MultiMesh.TRANSFORM_3D
|
||||||
|
mm.instance_count = 60
|
||||||
|
trajectory_mesh.multimesh = mm
|
||||||
|
var dot_mesh = SphereMesh.new()
|
||||||
|
dot_mesh.radius = 0.05
|
||||||
|
dot_mesh.height = 0.1
|
||||||
|
var dot_mat = StandardMaterial3D.new()
|
||||||
|
dot_mat.albedo_color = Color(1.0, 1.0, 1.0, 0.5)
|
||||||
|
dot_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
dot_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
dot_mesh.material = dot_mat
|
||||||
|
trajectory_mesh.multimesh.mesh = dot_mesh
|
||||||
|
trajectory_mesh.visible = false
|
||||||
|
get_tree().current_scene.call_deferred("add_child", trajectory_mesh)
|
||||||
|
|
||||||
var _last_hit_sound_time: int = 0
|
var _last_hit_sound_time: int = 0
|
||||||
|
|
||||||
func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
|
func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
|
||||||
@@ -645,7 +674,17 @@ func server_receive_inputs(input_dir: Vector2, wish_dir: Vector3, jump: bool, ju
|
|||||||
func _physics_process(_delta: float) -> void:
|
func _physics_process(_delta: float) -> void:
|
||||||
# Local client captures input and sends it
|
# Local client captures input and sends it
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
var raw_input := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
var raw_input := Vector2.ZERO
|
||||||
|
var input_jump := false
|
||||||
|
var input_jump_just := false
|
||||||
|
var input_crouch := false
|
||||||
|
var input_dash := false
|
||||||
|
var input_grapple := false
|
||||||
|
var input_grapple_just := false
|
||||||
|
var world_dir := Vector3.ZERO
|
||||||
|
|
||||||
|
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||||
|
raw_input = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||||
var forward := -global_transform.basis.z
|
var forward := -global_transform.basis.z
|
||||||
forward.y = 0.0
|
forward.y = 0.0
|
||||||
forward = forward.normalized()
|
forward = forward.normalized()
|
||||||
@@ -653,16 +692,16 @@ func _physics_process(_delta: float) -> void:
|
|||||||
right.y = 0.0
|
right.y = 0.0
|
||||||
right = right.normalized()
|
right = right.normalized()
|
||||||
|
|
||||||
var world_dir := (forward * (-raw_input.y) + right * raw_input.x)
|
world_dir = (forward * (-raw_input.y) + right * raw_input.x)
|
||||||
if world_dir.length_squared() > 1.0:
|
if world_dir.length_squared() > 1.0:
|
||||||
world_dir = world_dir.normalized()
|
world_dir = world_dir.normalized()
|
||||||
|
|
||||||
var input_jump = Input.is_action_pressed("jump")
|
input_jump = Input.is_action_pressed("jump")
|
||||||
var input_jump_just = Input.is_action_just_pressed("jump")
|
input_jump_just = Input.is_action_just_pressed("jump")
|
||||||
var input_crouch = Input.is_action_pressed("crouch")
|
input_crouch = Input.is_action_pressed("crouch")
|
||||||
var input_dash = Input.is_action_just_pressed("dash")
|
input_dash = Input.is_action_just_pressed("dash")
|
||||||
var input_grapple = Input.is_action_pressed("grapple")
|
input_grapple = Input.is_action_pressed("grapple")
|
||||||
var input_grapple_just = Input.is_action_just_pressed("grapple")
|
input_grapple_just = Input.is_action_just_pressed("grapple")
|
||||||
|
|
||||||
if Input.is_action_just_pressed("toggle_flashlight") and is_instance_valid(flashlight):
|
if Input.is_action_just_pressed("toggle_flashlight") and is_instance_valid(flashlight):
|
||||||
flashlight.visible = !flashlight.visible
|
flashlight.visible = !flashlight.visible
|
||||||
@@ -770,6 +809,26 @@ func _process(delta: float) -> void:
|
|||||||
shield_bar.value = shield
|
shield_bar.value = shield
|
||||||
shield_label.text = "%d / %d" % [ceil(shield), max_shield]
|
shield_label.text = "%d / %d" % [ceil(shield), max_shield]
|
||||||
|
|
||||||
|
# Update Reload Ring
|
||||||
|
if is_instance_valid(reload_ring) and is_instance_valid(camera):
|
||||||
|
var wman = camera.get_node_or_null("WeaponManager")
|
||||||
|
if wman:
|
||||||
|
var slot = wman.get("active_slot")
|
||||||
|
if slot != null and wman.weapons.has(slot):
|
||||||
|
var w = wman.weapons[slot]
|
||||||
|
if "reloading" in w and w.reloading and "reload_timer" in w and "reload_time" in w:
|
||||||
|
reload_ring.progress = 1.0 - (w.reload_timer / w.reload_time)
|
||||||
|
else:
|
||||||
|
reload_ring.progress = 0.0
|
||||||
|
else:
|
||||||
|
reload_ring.progress = 0.0
|
||||||
|
|
||||||
|
if is_multiplayer_authority() and not is_dead:
|
||||||
|
if is_holding_grenade and grenades > 0:
|
||||||
|
_draw_trajectory()
|
||||||
|
elif is_instance_valid(trajectory_mesh):
|
||||||
|
trajectory_mesh.visible = false
|
||||||
|
|
||||||
if is_dead:
|
if is_dead:
|
||||||
# Continuously follow the ragdoll torso
|
# Continuously follow the ragdoll torso
|
||||||
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
||||||
@@ -1004,6 +1063,87 @@ func _input(event: InputEvent) -> void:
|
|||||||
if event.is_pressed() and not event.is_echo():
|
if event.is_pressed() and not event.is_echo():
|
||||||
_on_respawn_pressed()
|
_on_respawn_pressed()
|
||||||
|
|
||||||
|
if not is_dead and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||||
|
if event.is_action_pressed("grenade") and grenades > 0:
|
||||||
|
is_holding_grenade = true
|
||||||
|
elif event.is_action_released("grenade") and is_holding_grenade:
|
||||||
|
is_holding_grenade = false
|
||||||
|
_throw_grenade()
|
||||||
|
|
||||||
|
func _draw_trajectory() -> void:
|
||||||
|
if not camera or not trajectory_mesh: return
|
||||||
|
trajectory_mesh.visible = true
|
||||||
|
|
||||||
|
var throw_speed = 20.0
|
||||||
|
var throw_up_speed = 5.0
|
||||||
|
var throw_vel = camera.global_transform.basis.z * -throw_speed + Vector3.UP * throw_up_speed + velocity
|
||||||
|
var gravity = 15.0 # Typical default
|
||||||
|
var current_pos = camera.global_position + camera.global_transform.basis.z * -0.5
|
||||||
|
var time_step = 0.05
|
||||||
|
var bounciness = 0.5
|
||||||
|
|
||||||
|
var space_state = get_world_3d().direct_space_state
|
||||||
|
|
||||||
|
for i in range(trajectory_mesh.multimesh.instance_count):
|
||||||
|
var t = Transform3D(Basis(), current_pos)
|
||||||
|
trajectory_mesh.multimesh.set_instance_transform(i, t)
|
||||||
|
|
||||||
|
var next_vel = throw_vel
|
||||||
|
next_vel.y -= gravity * time_step
|
||||||
|
var next_pos = current_pos + throw_vel * time_step
|
||||||
|
|
||||||
|
var query = PhysicsRayQueryParameters3D.create(current_pos, next_pos)
|
||||||
|
query.exclude = [get_rid()]
|
||||||
|
var hit = space_state.intersect_ray(query)
|
||||||
|
|
||||||
|
if hit:
|
||||||
|
current_pos = hit.position + hit.normal * 0.05
|
||||||
|
throw_vel = next_vel.bounce(hit.normal) * bounciness
|
||||||
|
else:
|
||||||
|
current_pos = next_pos
|
||||||
|
throw_vel = next_vel
|
||||||
|
|
||||||
|
func _throw_grenade() -> void:
|
||||||
|
if grenades <= 0: return
|
||||||
|
grenades -= 1
|
||||||
|
|
||||||
|
var throw_speed = 20.0
|
||||||
|
var throw_up_speed = 5.0
|
||||||
|
var throw_vel = camera.global_transform.basis.z * -throw_speed + Vector3.UP * throw_up_speed + velocity
|
||||||
|
var spawn_pos = camera.global_position + camera.global_transform.basis.z * -0.5
|
||||||
|
|
||||||
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||||
|
server_spawn_grenade.rpc_id(1, spawn_pos, throw_vel)
|
||||||
|
else:
|
||||||
|
_spawn_grenade_local(spawn_pos, throw_vel, 1)
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func server_spawn_grenade(pos: Vector3, vel: Vector3) -> void:
|
||||||
|
if not multiplayer.is_server(): return
|
||||||
|
var id = multiplayer.get_remote_sender_id()
|
||||||
|
client_spawn_grenade.rpc(pos, vel, id)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func client_spawn_grenade(pos: Vector3, vel: Vector3, owner_id: int) -> void:
|
||||||
|
_spawn_grenade_local(pos, vel, owner_id)
|
||||||
|
|
||||||
|
func _spawn_grenade_local(pos: Vector3, vel: Vector3, owner_id: int) -> void:
|
||||||
|
var g_scene = load("res://weapons/grenade_projectile.gd")
|
||||||
|
if not g_scene: return
|
||||||
|
var g = g_scene.new()
|
||||||
|
g.global_position = pos
|
||||||
|
g.velocity = vel
|
||||||
|
|
||||||
|
var p = null
|
||||||
|
if owner_id == multiplayer.get_unique_id():
|
||||||
|
p = self
|
||||||
|
else:
|
||||||
|
var root = get_tree().current_scene
|
||||||
|
if root.has_node("Players/" + str(owner_id)):
|
||||||
|
p = root.get_node("Players/" + str(owner_id))
|
||||||
|
g.owner_player = p
|
||||||
|
get_tree().current_scene.add_child(g)
|
||||||
|
|
||||||
func _on_respawn_pressed() -> void:
|
func _on_respawn_pressed() -> void:
|
||||||
if death_screen:
|
if death_screen:
|
||||||
death_screen.visible = false
|
death_screen.visible = false
|
||||||
@@ -1042,6 +1182,8 @@ func rpc_respawn(spawn_pos: Vector3) -> void:
|
|||||||
health = max_health
|
health = max_health
|
||||||
shield = max_shield
|
shield = max_shield
|
||||||
time_since_last_damage = 0.0
|
time_since_last_damage = 0.0
|
||||||
|
grenades = 2
|
||||||
|
is_holding_grenade = false
|
||||||
|
|
||||||
if is_instance_valid(ragdoll_instance):
|
if is_instance_valid(ragdoll_instance):
|
||||||
if ragdoll_instance.has_meta("spring_arm"):
|
if ragdoll_instance.has_meta("spring_arm"):
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func update(delta: float) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# ── Wall interaction (Run, Climb, Vault) ──────────────────────────────
|
# ── Wall interaction (Run, Climb, Vault) ──────────────────────────────
|
||||||
if machine.input_dir.length() > 0.1 and machine.wall_cooldown_timer <= 0.0:
|
if machine.input_dir.length() > 0.1 and machine.input_dir.y <= 0.0 and machine.wall_cooldown_timer <= 0.0:
|
||||||
var fwd_wall = machine.detect_wall_forward()
|
var fwd_wall = machine.detect_wall_forward()
|
||||||
if fwd_wall.hit:
|
if fwd_wall.hit:
|
||||||
if fwd_wall.is_short:
|
if fwd_wall.is_short:
|
||||||
@@ -132,7 +132,7 @@ func update(delta: float) -> void:
|
|||||||
var looking_towards := look_dir.dot(-fwd_wall.normal) > 0.0
|
var looking_towards := look_dir.dot(-fwd_wall.normal) > 0.0
|
||||||
var moving_towards := wish_dir.dot(-fwd_wall.normal) > cos(deg_to_rad(params.wall_climb_max_angle))
|
var moving_towards := wish_dir.dot(-fwd_wall.normal) > cos(deg_to_rad(params.wall_climb_max_angle))
|
||||||
|
|
||||||
if looking_towards and moving_towards:
|
if looking_towards and moving_towards and machine.can_wall_climb:
|
||||||
machine.wall_normal = fwd_wall.normal
|
machine.wall_normal = fwd_wall.normal
|
||||||
machine.switch_to("wall_climb")
|
machine.switch_to("wall_climb")
|
||||||
return
|
return
|
||||||
@@ -140,6 +140,12 @@ func update(delta: float) -> void:
|
|||||||
# Fallback to wall run
|
# Fallback to wall run
|
||||||
var wall_n := machine.detect_wall_horizontal()
|
var wall_n := machine.detect_wall_horizontal()
|
||||||
if wall_n != Vector3.ZERO:
|
if wall_n != Vector3.ZERO:
|
||||||
|
var w_look_dir := -player.global_transform.basis.z
|
||||||
|
var h_look := Vector3(w_look_dir.x, 0.0, w_look_dir.z).normalized()
|
||||||
|
|
||||||
|
# Require looking mostly along the wall (angle > 41 degrees from normal)
|
||||||
|
# and inputting mostly along the wall (angle > 31 degrees from normal, allows W+A/D diagonally into wall)
|
||||||
|
if absf(h_look.dot(wall_n)) < 0.75 and absf(wish_dir.dot(wall_n)) < 0.85:
|
||||||
machine.switch_to("wall_run")
|
machine.switch_to("wall_run")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var _footstep_timer: float = 0.0
|
|||||||
func enter(_data: Dictionary = {}) -> void:
|
func enter(_data: Dictionary = {}) -> void:
|
||||||
machine.on_ground = true
|
machine.on_ground = true
|
||||||
machine.current_jump_count = 0
|
machine.current_jump_count = 0
|
||||||
|
machine.can_wall_climb = true
|
||||||
# Clear wall tilt on landing
|
# Clear wall tilt on landing
|
||||||
var rig = _get_camera_rig()
|
var rig = _get_camera_rig()
|
||||||
if rig:
|
if rig:
|
||||||
@@ -96,13 +97,13 @@ func update(delta: float) -> void:
|
|||||||
for i in range(player.get_slide_collision_count()):
|
for i in range(player.get_slide_collision_count()):
|
||||||
var col = player.get_slide_collision(i)
|
var col = player.get_slide_collision(i)
|
||||||
if absf(col.get_normal().y) < 0.3: # Hit a wall
|
if absf(col.get_normal().y) < 0.3: # Hit a wall
|
||||||
var max_step_height = 0.5
|
var max_step_height = 0.95
|
||||||
var space_state = player.get_world_3d().direct_space_state
|
var space_state = player.get_world_3d().direct_space_state
|
||||||
var feet_y = player.global_position.y - (machine.original_capsule_height / 2.0)
|
var feet_y = player.global_position.y - (machine.original_capsule_height / 2.0)
|
||||||
|
|
||||||
# Raycast down from above the step
|
# Raycast down from above the step
|
||||||
var fwd = wish_dir.normalized()
|
var fwd = wish_dir.normalized()
|
||||||
var ray_start = player.global_position + fwd * 0.5
|
var ray_start = player.global_position + fwd * 0.65
|
||||||
ray_start.y = feet_y + max_step_height + 0.1
|
ray_start.y = feet_y + max_step_height + 0.1
|
||||||
var ray_end = ray_start - Vector3.UP * (max_step_height + 0.2)
|
var ray_end = ray_start - Vector3.UP * (max_step_height + 0.2)
|
||||||
|
|
||||||
@@ -121,8 +122,14 @@ func update(delta: float) -> void:
|
|||||||
head_ray.exclude = [player.get_rid()]
|
head_ray.exclude = [player.get_rid()]
|
||||||
|
|
||||||
if space_state.intersect_ray(head_ray).is_empty():
|
if space_state.intersect_ray(head_ray).is_empty():
|
||||||
|
# Check if there is enough space to move forward after stepping up
|
||||||
|
var test_transform = player.global_transform
|
||||||
|
test_transform.origin.y += step_height + 0.05
|
||||||
|
if not player.test_move(test_transform, fwd * 0.15):
|
||||||
# We can safely step up
|
# We can safely step up
|
||||||
player.global_position.y += step_height + 0.01
|
player.global_position.y += step_height + 0.01
|
||||||
|
if player.head_pivot and player.head_pivot.has_method("add_step_offset"):
|
||||||
|
player.head_pivot.add_step_offset(-(step_height + 0.01))
|
||||||
# Push slightly forward to get onto the step
|
# Push slightly forward to get onto the step
|
||||||
player.global_position += fwd * 0.1
|
player.global_position += fwd * 0.1
|
||||||
# Restore horizontal velocity that was lost from hitting the wall
|
# Restore horizontal velocity that was lost from hitting the wall
|
||||||
@@ -156,7 +163,7 @@ func update(delta: float) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# ── Wall interaction (Run, Climb, Vault) ──────────────────────────────
|
# ── Wall interaction (Run, Climb, Vault) ──────────────────────────────
|
||||||
if machine.input_dir.length() > 0.1 and machine.wall_cooldown_timer <= 0.0 and not machine.input_crouch:
|
if machine.input_dir.length() > 0.1 and machine.input_dir.y <= 0.0 and machine.wall_cooldown_timer <= 0.0 and not machine.input_crouch:
|
||||||
var fwd_wall = machine.detect_wall_forward()
|
var fwd_wall = machine.detect_wall_forward()
|
||||||
if fwd_wall.hit:
|
if fwd_wall.hit:
|
||||||
if fwd_wall.is_short:
|
if fwd_wall.is_short:
|
||||||
@@ -188,7 +195,7 @@ func update(delta: float) -> void:
|
|||||||
var looking_towards := look_dir.dot(-fwd_wall.normal) > 0.0
|
var looking_towards := look_dir.dot(-fwd_wall.normal) > 0.0
|
||||||
var moving_towards := wish_dir.dot(-fwd_wall.normal) > cos(deg_to_rad(params.wall_climb_max_angle))
|
var moving_towards := wish_dir.dot(-fwd_wall.normal) > cos(deg_to_rad(params.wall_climb_max_angle))
|
||||||
|
|
||||||
if looking_towards and moving_towards:
|
if looking_towards and moving_towards and machine.can_wall_climb:
|
||||||
machine.wall_normal = fwd_wall.normal
|
machine.wall_normal = fwd_wall.normal
|
||||||
machine.switch_to("wall_climb")
|
machine.switch_to("wall_climb")
|
||||||
return
|
return
|
||||||
@@ -197,6 +204,12 @@ func update(delta: float) -> void:
|
|||||||
var wall_n := machine.detect_wall_horizontal()
|
var wall_n := machine.detect_wall_horizontal()
|
||||||
if wall_n != Vector3.ZERO:
|
if wall_n != Vector3.ZERO:
|
||||||
if not player.is_on_floor():
|
if not player.is_on_floor():
|
||||||
|
var w_look_dir := -player.global_transform.basis.z
|
||||||
|
var h_look := Vector3(w_look_dir.x, 0.0, w_look_dir.z).normalized()
|
||||||
|
|
||||||
|
# Require looking mostly along the wall (angle > 41 degrees from normal)
|
||||||
|
# and inputting mostly along the wall (angle > 31 degrees from normal, allows W+A/D diagonally into wall)
|
||||||
|
if absf(h_look.dot(wall_n)) < 0.75 and absf(wish_dir.dot(wall_n)) < 0.85:
|
||||||
machine.switch_to("wall_run")
|
machine.switch_to("wall_run")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ func _detach() -> void:
|
|||||||
machine.wall_normal = Vector3.ZERO
|
machine.wall_normal = Vector3.ZERO
|
||||||
machine.wall_side = 0.0
|
machine.wall_side = 0.0
|
||||||
machine.wall_cooldown_timer = 0.3
|
machine.wall_cooldown_timer = 0.3
|
||||||
|
machine.can_wall_climb = false
|
||||||
machine.switch_to("air")
|
machine.switch_to("air")
|
||||||
|
|
||||||
func _vault() -> void:
|
func _vault() -> void:
|
||||||
|
|||||||
+12
-7
@@ -22,6 +22,13 @@ LoadoutManager="*res://globals/loadout_manager.gd"
|
|||||||
PauseMenu="*res://ui/pause_menu.gd"
|
PauseMenu="*res://ui/pause_menu.gd"
|
||||||
NetworkManager="*res://globals/network_manager.gd"
|
NetworkManager="*res://globals/network_manager.gd"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/viewport_width=1920
|
||||||
|
window/size/viewport_height=1080
|
||||||
|
window/stretch/mode="canvas_items"
|
||||||
|
window/stretch/aspect="expand"
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
move_forward={
|
move_forward={
|
||||||
@@ -79,6 +86,11 @@ interact={
|
|||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
grenade={
|
||||||
|
"deadzone": 0.0,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
reload={
|
reload={
|
||||||
"deadzone": 0.0,
|
"deadzone": 0.0,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
@@ -102,10 +114,3 @@ scoreboard={
|
|||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
rendering_device/driver.windows="d3d12"
|
rendering_device/driver.windows="d3d12"
|
||||||
|
|
||||||
[display]
|
|
||||||
|
|
||||||
window/stretch/mode="canvas_items"
|
|
||||||
window/stretch/aspect="expand"
|
|
||||||
window/size/viewport_width=1920
|
|
||||||
window/size/viewport_height=1080
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var _dash_icon: TextureRect
|
|||||||
var _grapple_label: Label
|
var _grapple_label: Label
|
||||||
var _dash_label: Label
|
var _dash_label: Label
|
||||||
var _fps_label: Label
|
var _fps_label: Label
|
||||||
|
var _standalone_speed_label: Label
|
||||||
var _player: CharacterBody3D
|
var _player: CharacterBody3D
|
||||||
var _debug_ui_panel: PanelContainer
|
var _debug_ui_panel: PanelContainer
|
||||||
|
|
||||||
@@ -283,6 +284,17 @@ func _build_hud() -> void:
|
|||||||
_fps_label.position = Vector2(12, 12)
|
_fps_label.position = Vector2(12, 12)
|
||||||
canvas.add_child(_fps_label)
|
canvas.add_child(_fps_label)
|
||||||
|
|
||||||
|
_standalone_speed_label = Label.new()
|
||||||
|
_standalone_speed_label.name = "StandaloneSpeedLabel"
|
||||||
|
_standalone_speed_label.text = "Speed: 0.0 m/s"
|
||||||
|
_standalone_speed_label.add_theme_font_size_override("font_size", 24)
|
||||||
|
_standalone_speed_label.add_theme_color_override("font_color", Color(0.3, 1.0, 0.5))
|
||||||
|
_standalone_speed_label.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
||||||
|
_standalone_speed_label.add_theme_constant_override("outline_size", 4)
|
||||||
|
_standalone_speed_label.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_standalone_speed_label.position = Vector2(12, 45)
|
||||||
|
canvas.add_child(_standalone_speed_label)
|
||||||
|
|
||||||
_debug_ui_panel = PanelContainer.new()
|
_debug_ui_panel = PanelContainer.new()
|
||||||
_debug_ui_panel.name = "InfoPanel"
|
_debug_ui_panel.name = "InfoPanel"
|
||||||
_debug_ui_panel.offset_left = 12
|
_debug_ui_panel.offset_left = 12
|
||||||
@@ -443,6 +455,17 @@ func _process(_delta: float) -> void:
|
|||||||
if _speed_label:
|
if _speed_label:
|
||||||
_speed_label.text = "Speed: %.1f m/s (total: %.1f)" % [hspeed, total_speed]
|
_speed_label.text = "Speed: %.1f m/s (total: %.1f)" % [hspeed, total_speed]
|
||||||
|
|
||||||
|
if _standalone_speed_label:
|
||||||
|
if SettingsManager.show_movement_speed and not SettingsManager.show_debug_ui:
|
||||||
|
_standalone_speed_label.visible = true
|
||||||
|
_standalone_speed_label.text = "Speed: %.1f m/s" % hspeed
|
||||||
|
if _fps_label and _fps_label.visible:
|
||||||
|
_standalone_speed_label.position = Vector2(12, 45)
|
||||||
|
else:
|
||||||
|
_standalone_speed_label.position = Vector2(12, 12)
|
||||||
|
else:
|
||||||
|
_standalone_speed_label.visible = false
|
||||||
|
|
||||||
if _state_label:
|
if _state_label:
|
||||||
var sm = _player.get_node_or_null("MovementStateMachine")
|
var sm = _player.get_node_or_null("MovementStateMachine")
|
||||||
if sm:
|
if sm:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var _dash_icon: TextureRect
|
|||||||
var _grapple_label: Label
|
var _grapple_label: Label
|
||||||
var _dash_label: Label
|
var _dash_label: Label
|
||||||
var _fps_label: Label
|
var _fps_label: Label
|
||||||
|
var _standalone_speed_label: Label
|
||||||
var _player: CharacterBody3D
|
var _player: CharacterBody3D
|
||||||
var _debug_ui_panel: PanelContainer
|
var _debug_ui_panel: PanelContainer
|
||||||
|
|
||||||
@@ -249,6 +250,17 @@ func _build_hud() -> void:
|
|||||||
_fps_label.position = Vector2(12, 12)
|
_fps_label.position = Vector2(12, 12)
|
||||||
canvas.add_child(_fps_label)
|
canvas.add_child(_fps_label)
|
||||||
|
|
||||||
|
_standalone_speed_label = Label.new()
|
||||||
|
_standalone_speed_label.name = "StandaloneSpeedLabel"
|
||||||
|
_standalone_speed_label.text = "Speed: 0.0 m/s"
|
||||||
|
_standalone_speed_label.add_theme_font_size_override("font_size", 24)
|
||||||
|
_standalone_speed_label.add_theme_color_override("font_color", Color(0.3, 1.0, 0.5))
|
||||||
|
_standalone_speed_label.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
||||||
|
_standalone_speed_label.add_theme_constant_override("outline_size", 4)
|
||||||
|
_standalone_speed_label.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_standalone_speed_label.position = Vector2(12, 45)
|
||||||
|
canvas.add_child(_standalone_speed_label)
|
||||||
|
|
||||||
_debug_ui_panel = PanelContainer.new()
|
_debug_ui_panel = PanelContainer.new()
|
||||||
_debug_ui_panel.name = "InfoPanel"
|
_debug_ui_panel.name = "InfoPanel"
|
||||||
_debug_ui_panel.offset_left = 12
|
_debug_ui_panel.offset_left = 12
|
||||||
@@ -423,6 +435,17 @@ func _process(_delta: float) -> void:
|
|||||||
if _speed_label:
|
if _speed_label:
|
||||||
_speed_label.text = "Speed: %.1f m/s (total: %.1f)" % [hspeed, total_speed]
|
_speed_label.text = "Speed: %.1f m/s (total: %.1f)" % [hspeed, total_speed]
|
||||||
|
|
||||||
|
if _standalone_speed_label:
|
||||||
|
if SettingsManager.show_movement_speed and not SettingsManager.show_debug_ui:
|
||||||
|
_standalone_speed_label.visible = true
|
||||||
|
_standalone_speed_label.text = "Speed: %.1f m/s" % hspeed
|
||||||
|
if _fps_label and _fps_label.visible:
|
||||||
|
_standalone_speed_label.position = Vector2(12, 45)
|
||||||
|
else:
|
||||||
|
_standalone_speed_label.position = Vector2(12, 12)
|
||||||
|
else:
|
||||||
|
_standalone_speed_label.visible = false
|
||||||
|
|
||||||
if _state_label:
|
if _state_label:
|
||||||
var sm = _player.get_node_or_null("MovementStateMachine")
|
var sm = _player.get_node_or_null("MovementStateMachine")
|
||||||
if sm:
|
if sm:
|
||||||
|
|||||||
+22
-4
@@ -37,6 +37,7 @@ var _resolution_opt: OptionButton
|
|||||||
var _fps_slider: HSlider
|
var _fps_slider: HSlider
|
||||||
var _fps_input: LineEdit
|
var _fps_input: LineEdit
|
||||||
var _show_fps_checkbox: CheckButton
|
var _show_fps_checkbox: CheckButton
|
||||||
|
var _show_movement_speed_checkbox: CheckButton
|
||||||
var _vol_master_slider: HSlider
|
var _vol_master_slider: HSlider
|
||||||
var _vol_master_input: LineEdit
|
var _vol_master_input: LineEdit
|
||||||
var _vol_sfx_slider: HSlider
|
var _vol_sfx_slider: HSlider
|
||||||
@@ -270,14 +271,16 @@ func _build_ui() -> void:
|
|||||||
var user_save = Button.new()
|
var user_save = Button.new()
|
||||||
user_save.text = "Save"
|
user_save.text = "Save"
|
||||||
user_save.pressed.connect(func():
|
user_save.pressed.connect(func():
|
||||||
SettingsManager.username = user_input.text
|
var final_name = user_input.text.strip_edges()
|
||||||
|
user_input.text = final_name
|
||||||
|
SettingsManager.username = final_name
|
||||||
SettingsManager._save_settings()
|
SettingsManager._save_settings()
|
||||||
var nm = get_node_or_null("/root/NetworkManager")
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
if nm:
|
if nm:
|
||||||
if nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
if nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
nm.update_username.rpc_id(1, user_input.text)
|
nm.update_username.rpc_id(1, final_name)
|
||||||
else:
|
else:
|
||||||
nm.update_username(user_input.text)
|
nm.update_username(final_name)
|
||||||
)
|
)
|
||||||
user_hbox.add_child(user_save)
|
user_hbox.add_child(user_save)
|
||||||
|
|
||||||
@@ -622,6 +625,15 @@ func _build_ui() -> void:
|
|||||||
)
|
)
|
||||||
advanced_tab.add_child(_show_fps_checkbox)
|
advanced_tab.add_child(_show_fps_checkbox)
|
||||||
|
|
||||||
|
_show_movement_speed_checkbox = CheckButton.new()
|
||||||
|
_show_movement_speed_checkbox.text = "Show Movement Speed Indicator"
|
||||||
|
_show_movement_speed_checkbox.button_pressed = SettingsManager.show_movement_speed
|
||||||
|
_show_movement_speed_checkbox.toggled.connect(func(toggled_on: bool):
|
||||||
|
SettingsManager.show_movement_speed = toggled_on
|
||||||
|
SettingsManager._save_settings()
|
||||||
|
)
|
||||||
|
advanced_tab.add_child(_show_movement_speed_checkbox)
|
||||||
|
|
||||||
var spacer_adv = Control.new()
|
var spacer_adv = Control.new()
|
||||||
spacer_adv.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
spacer_adv.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
advanced_tab.add_child(spacer_adv)
|
advanced_tab.add_child(spacer_adv)
|
||||||
@@ -633,6 +645,8 @@ func _build_ui() -> void:
|
|||||||
SettingsManager.show_debug_ui = false
|
SettingsManager.show_debug_ui = false
|
||||||
_show_fps_checkbox.button_pressed = false
|
_show_fps_checkbox.button_pressed = false
|
||||||
SettingsManager.show_fps = false
|
SettingsManager.show_fps = false
|
||||||
|
_show_movement_speed_checkbox.button_pressed = false
|
||||||
|
SettingsManager.show_movement_speed = false
|
||||||
SettingsManager._save_settings()
|
SettingsManager._save_settings()
|
||||||
)
|
)
|
||||||
advanced_tab.add_child(reset_adv_btn)
|
advanced_tab.add_child(reset_adv_btn)
|
||||||
@@ -784,6 +798,8 @@ func _show_settings() -> void:
|
|||||||
_debug_ui_checkbox.button_pressed = SettingsManager.show_debug_ui
|
_debug_ui_checkbox.button_pressed = SettingsManager.show_debug_ui
|
||||||
if _show_fps_checkbox:
|
if _show_fps_checkbox:
|
||||||
_show_fps_checkbox.button_pressed = SettingsManager.show_fps
|
_show_fps_checkbox.button_pressed = SettingsManager.show_fps
|
||||||
|
if _show_movement_speed_checkbox:
|
||||||
|
_show_movement_speed_checkbox.button_pressed = SettingsManager.show_movement_speed
|
||||||
if _world_fov_slider:
|
if _world_fov_slider:
|
||||||
_world_fov_slider.value = SettingsManager.world_fov
|
_world_fov_slider.value = SettingsManager.world_fov
|
||||||
_world_fov_input.text = str(round(SettingsManager.world_fov))
|
_world_fov_input.text = str(round(SettingsManager.world_fov))
|
||||||
@@ -906,7 +922,9 @@ func _populate_options(opt: OptionButton, category: String, current_id: String)
|
|||||||
func _save_current_loadout() -> void:
|
func _save_current_loadout() -> void:
|
||||||
if editing_index >= 0 and editing_index < LoadoutManager.loadouts.size():
|
if editing_index >= 0 and editing_index < LoadoutManager.loadouts.size():
|
||||||
var l = LoadoutManager.loadouts[editing_index]
|
var l = LoadoutManager.loadouts[editing_index]
|
||||||
l["name"] = name_edit.text
|
if name_edit.text.strip_edges() != "":
|
||||||
|
l["name"] = name_edit.text.strip_edges()
|
||||||
|
name_edit.text = l["name"]
|
||||||
l["primary_1"] = primary1_opt.get_item_metadata(primary1_opt.get_selected_id())
|
l["primary_1"] = primary1_opt.get_item_metadata(primary1_opt.get_selected_id())
|
||||||
l["primary_2"] = primary2_opt.get_item_metadata(primary2_opt.get_selected_id())
|
l["primary_2"] = primary2_opt.get_item_metadata(primary2_opt.get_selected_id())
|
||||||
l["special"] = special_opt.get_item_metadata(special_opt.get_selected_id())
|
l["special"] = special_opt.get_item_metadata(special_opt.get_selected_id())
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
extends Control
|
||||||
|
class_name ReloadRing
|
||||||
|
|
||||||
|
var progress: float = 0.0:
|
||||||
|
set(val):
|
||||||
|
progress = clampf(val, 0.0, 1.0)
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
var radius: float = 16.0
|
||||||
|
var thickness: float = 4.0
|
||||||
|
var color: Color = Color(1.0, 1.0, 1.0, 0.8)
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if progress <= 0.0 or progress >= 1.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Draw background arc (darker)
|
||||||
|
draw_arc(Vector2.ZERO, radius, 0, PI * 2.0, 32, Color(0, 0, 0, 0.4), thickness, true)
|
||||||
|
|
||||||
|
# Draw progress arc
|
||||||
|
var end_angle = -PI / 2.0 + (PI * 2.0 * progress)
|
||||||
|
draw_arc(Vector2.ZERO, radius, -PI / 2.0, end_angle, 32, color, thickness, true)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c5eanbqnnm4kn
|
||||||
@@ -11,6 +11,7 @@ class_name BaseHitscanWeapon
|
|||||||
@export var max_range: float = 100.0
|
@export var max_range: float = 100.0
|
||||||
@export var automatic: bool = true
|
@export var automatic: bool = true
|
||||||
@export var spread_angle: float = 0.01
|
@export var spread_angle: float = 0.01
|
||||||
|
@export var recoil_amplitude: float = 0.05
|
||||||
|
|
||||||
@export var penetration_count: int = 0
|
@export var penetration_count: int = 0
|
||||||
@export var penetration_damage_penalty: float = 0.5
|
@export var penetration_damage_penalty: float = 0.5
|
||||||
@@ -46,11 +47,15 @@ func _process(delta: float) -> void:
|
|||||||
fire_cooldown -= delta
|
fire_cooldown -= delta
|
||||||
|
|
||||||
if reloading:
|
if reloading:
|
||||||
reload_timer -= delta
|
var spin_speed = PI * 8.0
|
||||||
var spin_speed = (PI * 2.0) / reload_time
|
|
||||||
model_root.rotation.x += spin_speed * delta
|
model_root.rotation.x += spin_speed * delta
|
||||||
|
|
||||||
if reload_timer <= 0.0:
|
if reload_timer > 0.0:
|
||||||
|
reload_timer -= delta
|
||||||
|
else:
|
||||||
|
# Continue the loop until facing normal direction (multiples of 2 PI)
|
||||||
|
var target_rot = ceil(model_root.rotation.x / (PI * 2.0)) * (PI * 2.0)
|
||||||
|
if target_rot - model_root.rotation.x < spin_speed * delta * 1.5:
|
||||||
current_ammo = max_ammo
|
current_ammo = max_ammo
|
||||||
reloading = false
|
reloading = false
|
||||||
model_root.rotation.x = 0.0
|
model_root.rotation.x = 0.0
|
||||||
@@ -93,6 +98,9 @@ func _fire() -> void:
|
|||||||
_shoot_hitscan()
|
_shoot_hitscan()
|
||||||
_play_muzzle_flash()
|
_play_muzzle_flash()
|
||||||
|
|
||||||
|
if get_parent() and get_parent().has_method("add_recoil"):
|
||||||
|
get_parent().add_recoil(recoil_amplitude, randf_range(-recoil_amplitude * 0.5, recoil_amplitude * 0.5))
|
||||||
|
|
||||||
if fire_sound:
|
if fire_sound:
|
||||||
# Slight pitch variation
|
# Slight pitch variation
|
||||||
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
|
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class_name BaseProjectileWeapon
|
|||||||
@export var max_range: float = 200.0
|
@export var max_range: float = 200.0
|
||||||
@export var automatic: bool = true
|
@export var automatic: bool = true
|
||||||
@export var spread_angle: float = 0.01
|
@export var spread_angle: float = 0.01
|
||||||
|
@export var recoil_amplitude: float = 0.05
|
||||||
|
|
||||||
@export var projectile_speed: float = 50.0
|
@export var projectile_speed: float = 50.0
|
||||||
@export var projectile_gravity: float = 0.0
|
@export var projectile_gravity: float = 0.0
|
||||||
@@ -45,11 +46,15 @@ func _process(delta: float) -> void:
|
|||||||
fire_cooldown -= delta
|
fire_cooldown -= delta
|
||||||
|
|
||||||
if reloading:
|
if reloading:
|
||||||
reload_timer -= delta
|
var spin_speed = PI * 8.0
|
||||||
var spin_speed = (PI * 2.0) / reload_time
|
|
||||||
model_root.rotation.x += spin_speed * delta
|
model_root.rotation.x += spin_speed * delta
|
||||||
|
|
||||||
if reload_timer <= 0.0:
|
if reload_timer > 0.0:
|
||||||
|
reload_timer -= delta
|
||||||
|
else:
|
||||||
|
# Continue the loop until facing normal direction (multiples of 2 PI)
|
||||||
|
var target_rot = ceil(model_root.rotation.x / (PI * 2.0)) * (PI * 2.0)
|
||||||
|
if target_rot - model_root.rotation.x < spin_speed * delta * 1.5:
|
||||||
current_ammo = max_ammo
|
current_ammo = max_ammo
|
||||||
reloading = false
|
reloading = false
|
||||||
model_root.rotation.x = 0.0
|
model_root.rotation.x = 0.0
|
||||||
@@ -92,6 +97,9 @@ func _fire() -> void:
|
|||||||
_shoot_projectile()
|
_shoot_projectile()
|
||||||
_play_muzzle_flash()
|
_play_muzzle_flash()
|
||||||
|
|
||||||
|
if get_parent() and get_parent().has_method("add_recoil"):
|
||||||
|
get_parent().add_recoil(recoil_amplitude, randf_range(-recoil_amplitude * 0.5, recoil_amplitude * 0.5))
|
||||||
|
|
||||||
if fire_sound:
|
if fire_sound:
|
||||||
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
|
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
|
||||||
fire_sound.play()
|
fire_sound.play()
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
extends BouncingProjectile
|
||||||
|
class_name GrenadeProjectile
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
fuse_time = 4.0
|
||||||
|
bounciness = 0.5
|
||||||
|
drop_gravity = 15.0
|
||||||
|
explosion_radius = 5.0
|
||||||
|
damage = 120.0
|
||||||
|
|
||||||
|
func _setup_effects() -> void:
|
||||||
|
# Audio
|
||||||
|
flight_sound = AudioStreamPlayer3D.new()
|
||||||
|
flight_sound.bus = "SFX"
|
||||||
|
flight_sound.stream = load("res://assets/sounds/rocket_flight.wav")
|
||||||
|
flight_sound.autoplay = true
|
||||||
|
if flight_sound.stream is AudioStreamWAV:
|
||||||
|
flight_sound.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||||
|
flight_sound.unit_size = 50.0
|
||||||
|
flight_sound.max_db = 6.0
|
||||||
|
flight_sound.volume_db = 0.0
|
||||||
|
add_child(flight_sound)
|
||||||
|
flight_sound.play()
|
||||||
|
|
||||||
|
# Grenade Body
|
||||||
|
var body_mesh = MeshInstance3D.new()
|
||||||
|
var sphere = SphereMesh.new()
|
||||||
|
sphere.radius = 0.1
|
||||||
|
sphere.height = 0.2
|
||||||
|
var b_mat = StandardMaterial3D.new()
|
||||||
|
b_mat.albedo_color = Color(0.2, 0.4, 0.2) # Dark green
|
||||||
|
b_mat.roughness = 0.6
|
||||||
|
b_mat.metallic = 0.5
|
||||||
|
sphere.material = b_mat
|
||||||
|
body_mesh.mesh = sphere
|
||||||
|
add_child(body_mesh)
|
||||||
|
|
||||||
|
# Smoke Trail (White translucent tail)
|
||||||
|
smoke_sys = GPUParticles3D.new()
|
||||||
|
var smoke_mat = ParticleProcessMaterial.new()
|
||||||
|
smoke_mat.direction = Vector3(0, 0, 1)
|
||||||
|
smoke_mat.spread = 5.0
|
||||||
|
smoke_mat.initial_velocity_min = 0.0
|
||||||
|
smoke_mat.initial_velocity_max = 0.5
|
||||||
|
smoke_mat.gravity = Vector3(0, 0.1, 0) # Less floaty
|
||||||
|
|
||||||
|
var grad = Gradient.new()
|
||||||
|
grad.add_point(0.0, Color(1.0, 1.0, 1.0, 0.5))
|
||||||
|
grad.add_point(1.0, Color(1.0, 1.0, 1.0, 0.0))
|
||||||
|
var grad_tex = GradientTexture1D.new()
|
||||||
|
grad_tex.gradient = grad
|
||||||
|
smoke_mat.color_ramp = grad_tex
|
||||||
|
|
||||||
|
var curve = Curve.new()
|
||||||
|
curve.add_point(Vector2(0.0, 0.5))
|
||||||
|
curve.add_point(Vector2(1.0, 1.5))
|
||||||
|
var curve_tex = CurveTexture.new()
|
||||||
|
curve_tex.curve = curve
|
||||||
|
smoke_mat.scale_curve = curve_tex
|
||||||
|
|
||||||
|
var smoke_mesh = QuadMesh.new()
|
||||||
|
smoke_mesh.size = Vector2(0.2, 0.2)
|
||||||
|
var s_mat = StandardMaterial3D.new()
|
||||||
|
s_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
s_mat.vertex_color_use_as_albedo = true
|
||||||
|
s_mat.albedo_texture = get_smoke_texture()
|
||||||
|
s_mat.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
||||||
|
s_mat.billboard_keep_scale = true
|
||||||
|
smoke_mesh.material = s_mat
|
||||||
|
smoke_sys.draw_pass_1 = smoke_mesh
|
||||||
|
smoke_sys.process_material = smoke_mat
|
||||||
|
smoke_sys.amount = 32
|
||||||
|
smoke_sys.lifetime = 1.0
|
||||||
|
smoke_sys.position = Vector3(0, 0, 0.0)
|
||||||
|
add_child(smoke_sys)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://3m8quupertk6
|
||||||
@@ -55,6 +55,21 @@ func _setup_viewmodel_viewport() -> void:
|
|||||||
dir_light.light_energy = 0.5
|
dir_light.light_energy = 0.5
|
||||||
add_child(dir_light)
|
add_child(dir_light)
|
||||||
|
|
||||||
|
var _bob_timer: float = 0.0
|
||||||
|
var _current_slide_tilt_z: float = 0.0
|
||||||
|
var _current_slide_tilt_x: float = 0.0
|
||||||
|
var _current_slide_offset_x: float = 0.0
|
||||||
|
var _current_slide_offset_y: float = 0.0
|
||||||
|
var _recoil_pitch: float = 0.0
|
||||||
|
var _recoil_yaw: float = 0.0
|
||||||
|
var _target_drift_offset: Vector3 = Vector3.ZERO
|
||||||
|
var _current_drift_offset: Vector3 = Vector3.ZERO
|
||||||
|
var _swap_pitch: float = 0.0
|
||||||
|
|
||||||
|
func add_recoil(pitch: float, yaw: float) -> void:
|
||||||
|
_recoil_pitch += pitch * 20.0 # Scale up for visual model recoil
|
||||||
|
_recoil_yaw += yaw * 20.0
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if is_instance_valid(vm_camera) and is_instance_valid(camera):
|
if is_instance_valid(vm_camera) and is_instance_valid(camera):
|
||||||
vm_camera.global_transform = camera.global_transform
|
vm_camera.global_transform = camera.global_transform
|
||||||
@@ -69,6 +84,72 @@ func _process(_delta: float) -> void:
|
|||||||
var over_speed_factor = clampf((hspeed - 11.0) / 19.0, 0.0, 1.0) # maxes out at 30 m/s
|
var over_speed_factor = clampf((hspeed - 11.0) / 19.0, 0.0, 1.0) # maxes out at 30 m/s
|
||||||
target_fov = lerpf(72.0, 80.0, over_speed_factor)
|
target_fov = lerpf(72.0, 80.0, over_speed_factor)
|
||||||
|
|
||||||
|
# Weapon Viewmodel Bobbing, Drift, and Slide Tilt
|
||||||
|
var is_sliding = player.has_method("get_node") and player.get_node_or_null("MovementStateMachine") and player.get_node("MovementStateMachine").current_state == "slide"
|
||||||
|
|
||||||
|
if is_sliding:
|
||||||
|
_bob_timer = 0.0
|
||||||
|
# Target tilt when sliding (inwards towards center, pitched slightly down)
|
||||||
|
# Assuming weapon is on right side (standard FPS), tilting left (positive Z rotation)
|
||||||
|
_current_slide_tilt_z = lerpf(_current_slide_tilt_z, deg_to_rad(15.0), 12.0 * _delta)
|
||||||
|
_current_slide_tilt_x = lerpf(_current_slide_tilt_x, 0.0, 12.0 * _delta)
|
||||||
|
# Move closer to center (negative X) and slightly down (negative Y)
|
||||||
|
_current_slide_offset_x = lerpf(_current_slide_offset_x, -0.15, 12.0 * _delta)
|
||||||
|
_current_slide_offset_y = lerpf(_current_slide_offset_y, -0.1, 12.0 * _delta)
|
||||||
|
else:
|
||||||
|
_current_slide_tilt_z = lerpf(_current_slide_tilt_z, 0.0, 10.0 * _delta)
|
||||||
|
_current_slide_tilt_x = lerpf(_current_slide_tilt_x, 0.0, 10.0 * _delta)
|
||||||
|
_current_slide_offset_x = lerpf(_current_slide_offset_x, 0.0, 10.0 * _delta)
|
||||||
|
_current_slide_offset_y = lerpf(_current_slide_offset_y, 0.0, 10.0 * _delta)
|
||||||
|
|
||||||
|
if player.is_on_floor() and hspeed > 1.0:
|
||||||
|
# Scale bobbing frequency by walk speed
|
||||||
|
_bob_timer += _delta * 12.0 * (hspeed / 11.0)
|
||||||
|
var bob_y = sin(_bob_timer) * 0.015
|
||||||
|
var bob_x = cos(_bob_timer * 0.5) * 0.01
|
||||||
|
vm_camera.translate_object_local(Vector3(bob_x, bob_y, 0))
|
||||||
|
else:
|
||||||
|
_bob_timer = 0.0
|
||||||
|
|
||||||
|
if not player.is_on_floor():
|
||||||
|
# Airborne Drift
|
||||||
|
# Convert player velocity into camera's local space
|
||||||
|
var local_vel = camera.global_transform.basis.inverse() * player.velocity
|
||||||
|
_target_drift_offset = Vector3(local_vel.x * 0.004, local_vel.y * 0.004, 0)
|
||||||
|
# Clamp the drift
|
||||||
|
_target_drift_offset.x = clampf(_target_drift_offset.x, -0.05, 0.05)
|
||||||
|
_target_drift_offset.y = clampf(_target_drift_offset.y, -0.05, 0.05)
|
||||||
|
else:
|
||||||
|
_target_drift_offset = Vector3.ZERO
|
||||||
|
|
||||||
|
# Smoothly apply drift so jumping/landing doesn't snap
|
||||||
|
_current_drift_offset = _current_drift_offset.lerp(_target_drift_offset, 15.0 * _delta)
|
||||||
|
|
||||||
|
vm_camera.translate_object_local(_current_drift_offset)
|
||||||
|
vm_camera.rotate_object_local(Vector3.UP, deg_to_rad(-_current_drift_offset.x * 30.0))
|
||||||
|
vm_camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(_current_drift_offset.y * 30.0))
|
||||||
|
|
||||||
|
# Apply slide tilt and offset
|
||||||
|
if absf(_current_slide_tilt_z) > 0.001 or absf(_current_slide_offset_x) > 0.001:
|
||||||
|
vm_camera.translate_object_local(Vector3(_current_slide_offset_x, _current_slide_offset_y, 0))
|
||||||
|
vm_camera.rotate_object_local(Vector3.FORWARD, _current_slide_tilt_z)
|
||||||
|
vm_camera.rotate_object_local(Vector3.RIGHT, _current_slide_tilt_x)
|
||||||
|
|
||||||
|
# Visual Recoil
|
||||||
|
_recoil_pitch = lerpf(_recoil_pitch, 0.0, 15.0 * _delta)
|
||||||
|
_recoil_yaw = lerpf(_recoil_yaw, 0.0, 15.0 * _delta)
|
||||||
|
|
||||||
|
vm_camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(_recoil_pitch))
|
||||||
|
vm_camera.rotate_object_local(Vector3.UP, deg_to_rad(_recoil_yaw))
|
||||||
|
# Add a slight backward kick along the local Z axis
|
||||||
|
vm_camera.translate_object_local(Vector3(0, 0, _recoil_pitch * 0.01))
|
||||||
|
|
||||||
|
# Swap Animation
|
||||||
|
_swap_pitch = lerpf(_swap_pitch, 0.0, 15.0 * _delta)
|
||||||
|
if _swap_pitch > 0.1:
|
||||||
|
vm_camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(_swap_pitch))
|
||||||
|
vm_camera.translate_object_local(Vector3(0, -_swap_pitch * 0.005, 0))
|
||||||
|
|
||||||
vm_camera.fov = lerpf(vm_camera.fov, target_fov, 10.0 * _delta)
|
vm_camera.fov = lerpf(vm_camera.fov, target_fov, 10.0 * _delta)
|
||||||
|
|
||||||
func _build_loadout() -> void:
|
func _build_loadout() -> void:
|
||||||
@@ -222,6 +303,9 @@ func _equip_slot(slot: int) -> void:
|
|||||||
if w.has_method("unequip"):
|
if w.has_method("unequip"):
|
||||||
w.unequip()
|
w.unequip()
|
||||||
|
|
||||||
|
if active_slot != slot:
|
||||||
|
_swap_pitch = 60.0
|
||||||
|
|
||||||
active_slot = slot
|
active_slot = slot
|
||||||
|
|
||||||
if weapons.has(active_slot):
|
if weapons.has(active_slot):
|
||||||
|
|||||||
Reference in New Issue
Block a user