Feat/2 weapons foundation #6

Merged
Dotts merged 30 commits from feat/2-weapons-foundation into main 2026-06-04 22:22:16 -07:00
6 changed files with 177 additions and 42 deletions
Showing only changes of commit 45fb7f0f38 - Show all commits
+14 -3
View File
@@ -64,8 +64,8 @@ func _build_input_map() -> void:
"sprint": [KEY_SHIFT],
"crouch": [KEY_CTRL],
"dash": [KEY_SHIFT],
"fire": [MOUSE_BUTTON_RIGHT, KEY_F],
"fire_alt": [MOUSE_BUTTON_LEFT],
"fire": [MOUSE_BUTTON_LEFT, KEY_F],
"fire_alt": [MOUSE_BUTTON_RIGHT],
"reload": [KEY_R],
"interact": [KEY_E],
"move_forward": [KEY_W],
@@ -262,8 +262,9 @@ func _build_lighting() -> void:
func _build_player() -> void:
var player := CharacterBody3D.new()
player.name = "Player"
player.position = Vector3(0, 1.0, 8)
player.position = Vector3(0, 1.0, 0)
add_child(player)
_player = player
# Collision shape
var col_shape := CollisionShape3D.new()
@@ -316,6 +317,16 @@ func _build_player() -> void:
camera.current = true
camera.fov = 90.0
head_pivot.add_child(camera)
# ── Temporary Weapon Setup ──────────────────────────────────────────────────
# TODO: Change how the player is given weapons in the future to a loadout schema.
var shotgun_script = preload("res://weapons/double_barrel_shotgun.gd")
if shotgun_script:
var shotgun = shotgun_script.new()
shotgun.name = "DoubleBarrelShotgun"
shotgun.player = player
shotgun.camera = camera
camera.add_child(shotgun)
var rig_script = preload("res://characters/player/fps_camera_rig.gd")
if rig_script:
+14 -11
View File
@@ -31,20 +31,23 @@ func update(delta: float) -> void:
# ── Air control ───────────────────────────────────────────────────────
var wish_dir: Vector3 = machine.wish_dir_world
var hvel := Vector3(vel.x, 0.0, vel.z)
var current_speed := hvel.length()
if wish_dir.length_squared() > 0.01:
var target_speed := maxf(current_speed, params.walk_speed)
target_speed = minf(target_speed, params.max_air_speed)
wish_dir = wish_dir.normalized()
var current_speed = hvel.length()
var target_vel := wish_dir * target_speed
var new_hvel = hvel.lerp(target_vel, 1.0 - exp(-params.air_control * params.air_acceleration * delta))
# Add a strong force in the wish direction
var air_accel = params.air_strafe_accel * delta
var new_hvel = hvel + wish_dir * air_accel
# Prevent speed loss during sharp turns, but allow braking if holding backward
if hvel.dot(target_vel) >= 0.0 and current_speed > params.walk_speed:
if new_hvel.length_squared() > 0.01:
new_hvel = new_hvel.normalized() * current_speed
# Limit the speed so we don't gain speed purely from air control.
# We only allow air control to change our direction (strafing)
# or to accelerate us up to our walk speed if we started slow.
var max_allowed_speed = maxf(current_speed, params.walk_speed)
if new_hvel.length() > max_allowed_speed:
new_hvel = new_hvel.normalized() * max_allowed_speed
hvel = new_hvel
else:
# No input: slight air drag (very subtle)
@@ -64,7 +67,7 @@ func update(delta: float) -> void:
# Bunny hop: if jump was buffered or pressed on landing frame
if machine.input_jump_pressed or machine.jump_buffer_time > 0.0:
var land_speed := Vector3(player.velocity.x, 0.0, player.velocity.z).length()
var bhop_speed := minf(land_speed + params.bunny_hop_speed_gain, params.bunny_hop_speed_cap)
var bhop_speed := maxf(land_speed, minf(land_speed + params.bunny_hop_speed_gain, params.bunny_hop_speed_cap))
# Maintain horizontal direction, boost speed
var hdir := Vector3(player.velocity.x, 0.0, player.velocity.z)
if hdir.length_squared() > 0.01:
+27 -27
View File
@@ -26,6 +26,33 @@ func update(delta: float) -> void:
var player := machine.player
var vel: Vector3 = player.velocity
# ── Jump (coyote time + jump buffer) ──────────────────────────────────
if machine.input_jump_just_pressed and machine.coyote_timer > 0.0:
player.velocity.y = params.jump_velocity
machine.current_jump_count = 1
machine.on_ground = false
machine.coyote_timer = 0.0
machine.register_chain_mechanic("jump")
machine.switch_to("air")
return
# Jump buffer: player pressed jump just before landing
if machine.on_ground and machine.jump_buffer_time > 0.0:
player.velocity.y = params.jump_velocity
machine.current_jump_count = 1
machine.on_ground = false
machine.jump_buffer_time = 0.0
machine.register_chain_mechanic("jump")
machine.switch_to("air")
return
# ── Slide (crouch while moving fast enough) ───────────────────────────
if machine.input_crouch and machine.slide_cooldown_timer <= 0.0:
var hspeed := Vector3(player.velocity.x, 0.0, player.velocity.z).length()
if hspeed > params.slide_min_speed:
machine.switch_to("slide")
return
# ── Determine target speed ────────────────────────────────────────────
var wish_dir: Vector3 = machine.wish_dir_world
var speed := params.walk_speed
@@ -66,38 +93,11 @@ func update(delta: float) -> void:
else:
machine.on_ground = false
# ── Jump (coyote time + jump buffer) ──────────────────────────────────
if machine.input_jump_just_pressed and machine.coyote_timer > 0.0:
player.velocity.y = params.jump_velocity
machine.current_jump_count = 1
machine.on_ground = false
machine.coyote_timer = 0.0
machine.register_chain_mechanic("jump")
machine.switch_to("air")
return
# Jump buffer: player pressed jump just before landing
if machine.on_ground and machine.jump_buffer_time > 0.0:
player.velocity.y = params.jump_velocity
machine.current_jump_count = 1
machine.on_ground = false
machine.jump_buffer_time = 0.0
machine.register_chain_mechanic("jump")
machine.switch_to("air")
return
# ── Falling off ledge → air state ─────────────────────────────────────
if not machine.on_ground and machine.coyote_timer <= 0.0:
machine.switch_to("air")
return
# ── Slide (crouch while moving fast enough) ───────────────────────────
if machine.input_crouch and machine.slide_cooldown_timer <= 0.0:
var hspeed := Vector3(player.velocity.x, 0.0, player.velocity.z).length()
if hspeed > params.slide_min_speed:
machine.switch_to("slide")
return
# ── Wall run check ────────────────────────────────────────────────────
var wall_n := machine.detect_wall_horizontal()
if wall_n != Vector3.ZERO and machine.input_dir.length() > 0.1:
+2 -1
View File
@@ -93,7 +93,8 @@ func update(delta: float) -> void:
var hvel := Vector3(player.velocity.x, 0.0, player.velocity.z)
if hvel.length_squared() > 0.01:
var dir := hvel.normalized()
var jump_speed := minf(hvel.length() + params.slide_jump_speed_boost, params.bunny_hop_speed_cap)
var current_speed := hvel.length()
var jump_speed := maxf(current_speed, minf(current_speed + params.slide_jump_speed_boost, params.bunny_hop_speed_cap))
player.velocity.x = dir.x * jump_speed
player.velocity.z = dir.z * jump_speed
machine.current_jump_count = 1
+119
View File
@@ -0,0 +1,119 @@
extends Node3D
class_name DoubleBarrelShotgun
@export var reload_time: float = 1.0
var shells: int = 2
var reloading: bool = false
var reload_timer: float = 0.0
var player: CharacterBody3D
var camera: Camera3D
func _ready() -> void:
set_process_input(true)
set_process(true)
_build_model()
func _build_model() -> void:
# Simple FPS weapon placement (bottom right)
position = Vector3(0.3, -0.3, -0.6)
# Stock
var stock = MeshInstance3D.new()
var stock_mesh = BoxMesh.new()
stock_mesh.size = Vector3(0.1, 0.1, 0.4)
var stock_mat = StandardMaterial3D.new()
stock_mat.albedo_color = Color(0.4, 0.2, 0.1) # Brown wood
stock_mesh.material = stock_mat
stock.mesh = stock_mesh
stock.position = Vector3(0, 0, 0.2)
add_child(stock)
# Left Barrel
var barrel_l = MeshInstance3D.new()
var barrel_mesh = CylinderMesh.new()
barrel_mesh.top_radius = 0.03
barrel_mesh.bottom_radius = 0.03
barrel_mesh.height = 0.6
var barrel_mat = StandardMaterial3D.new()
barrel_mat.albedo_color = Color(0.2, 0.2, 0.2) # Dark grey metal
barrel_mat.metallic = 0.8
barrel_mat.roughness = 0.4
barrel_mesh.material = barrel_mat
barrel_l.mesh = barrel_mesh
barrel_l.rotation.x = deg_to_rad(90)
barrel_l.position = Vector3(-0.04, 0.03, -0.1)
add_child(barrel_l)
# Right Barrel
var barrel_r = MeshInstance3D.new()
barrel_r.mesh = barrel_mesh
barrel_r.rotation.x = deg_to_rad(90)
barrel_r.position = Vector3(0.04, 0.03, -0.1)
add_child(barrel_r)
func _process(delta: float) -> void:
if reloading:
reload_timer -= delta
# Spin vertically (flip) over the duration of the reload
var spin_speed = (PI * 2.0) / reload_time
rotation.x += spin_speed * delta
if reload_timer <= 0.0:
shells = 2
reloading = false
rotation.x = 0.0 # Snap back to perfectly level
print("Shotgun Reloaded!")
func _input(event: InputEvent) -> void:
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
return
if event.is_action_pressed("fire"):
_try_fire()
elif event.is_action_released("fire"):
_try_fire()
if event.is_action_pressed("reload"):
if shells < 2 and not reloading:
reloading = true
reload_timer = reload_time
func _try_fire() -> void:
if shells > 0 and not reloading:
shells -= 1
print("BANG! Shells left: ", shells)
_apply_impulse()
# Start reload timer if we just shot the LAST shell
if shells == 0:
reloading = true
reload_timer = reload_time
elif shells == 0 and not reloading:
# Empty click, force reload
reloading = true
reload_timer = reload_time
func _apply_impulse() -> void:
if not player or not camera:
return
# Get the shotgun impulse parameter from the player's movement controller
var impulse_strength = 20.0
if player.has_method("get") and "params" in player:
if player.params and "shotgun_jump_impulse" in player.params:
impulse_strength = player.params.shotgun_jump_impulse
# Impulse direction is opposite to the camera's forward direction
var push_dir = camera.global_transform.basis.z.normalized()
# Apply impulse to player velocity
player.velocity += push_dir * impulse_strength
# Small hack to ensure we aren't considered on floor if we shoot down
if push_dir.y > 0:
# Just slightly lift the player to break floor contact immediately
player.position.y += 0.05
+1
View File
@@ -0,0 +1 @@
uid://chgdeglyrlf07