feat: add plasma gun weapon implementation and new pause menu system with loadout management
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
extends Node3D
|
||||
class_name BaseHitscanWeapon
|
||||
|
||||
@export var weapon_name: String = "Base Hitscan"
|
||||
@export var fire_rate: float = 0.1 # Seconds between shots
|
||||
@export var max_ammo: int = 30
|
||||
@export var reload_time: float = 1.5
|
||||
@export var base_damage: float = 10.0
|
||||
@export var min_damage: float = 5.0
|
||||
@export var falloff_start: float = 10.0
|
||||
@export var max_range: float = 100.0
|
||||
@export var automatic: bool = true
|
||||
@export var spread_angle: float = 0.01
|
||||
|
||||
var current_ammo: int = 30
|
||||
var reloading: bool = false
|
||||
var reload_timer: float = 0.0
|
||||
var fire_cooldown: float = 0.0
|
||||
var is_firing: bool = false
|
||||
|
||||
var player: CharacterBody3D
|
||||
var camera: Camera3D
|
||||
var muzzle_flash: OmniLight3D
|
||||
var fire_sound: AudioStreamPlayer3D
|
||||
var reload_sound: AudioStreamPlayer3D
|
||||
var model_root: Node3D
|
||||
|
||||
func _ready() -> void:
|
||||
current_ammo = max_ammo
|
||||
set_process_input(true)
|
||||
set_process(true)
|
||||
|
||||
model_root = Node3D.new()
|
||||
add_child(model_root)
|
||||
_build_model()
|
||||
|
||||
func _build_model() -> void:
|
||||
# Virtual method to be overridden
|
||||
pass
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if fire_cooldown > 0.0:
|
||||
fire_cooldown -= delta
|
||||
|
||||
if reloading:
|
||||
reload_timer -= delta
|
||||
var spin_speed = (PI * 2.0) / reload_time
|
||||
model_root.rotation.x += spin_speed * delta
|
||||
|
||||
if reload_timer <= 0.0:
|
||||
current_ammo = max_ammo
|
||||
reloading = false
|
||||
model_root.rotation.x = 0.0
|
||||
print(weapon_name, " Reloaded!")
|
||||
|
||||
elif automatic and is_firing and fire_cooldown <= 0.0 and current_ammo > 0:
|
||||
_fire()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||
return
|
||||
|
||||
if event.is_action_pressed("fire"):
|
||||
is_firing = true
|
||||
if not automatic and fire_cooldown <= 0.0 and current_ammo > 0 and not reloading:
|
||||
_fire()
|
||||
elif current_ammo == 0 and not reloading:
|
||||
_start_reload()
|
||||
elif event.is_action_released("fire"):
|
||||
is_firing = false
|
||||
|
||||
if event.is_action_pressed("reload"):
|
||||
if current_ammo < max_ammo and not reloading:
|
||||
_start_reload()
|
||||
|
||||
func _start_reload() -> void:
|
||||
reloading = true
|
||||
reload_timer = reload_time
|
||||
is_firing = false
|
||||
if reload_sound:
|
||||
reload_sound.play()
|
||||
|
||||
func _fire() -> void:
|
||||
if current_ammo <= 0 or reloading:
|
||||
return
|
||||
|
||||
current_ammo -= 1
|
||||
fire_cooldown = fire_rate
|
||||
|
||||
_shoot_hitscan()
|
||||
_play_muzzle_flash()
|
||||
|
||||
if fire_sound:
|
||||
# Slight pitch variation
|
||||
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
|
||||
fire_sound.play()
|
||||
|
||||
if current_ammo == 0:
|
||||
_start_reload()
|
||||
|
||||
func _play_muzzle_flash() -> void:
|
||||
if muzzle_flash:
|
||||
muzzle_flash.light_energy = 8.0
|
||||
var tween = create_tween()
|
||||
tween.tween_property(muzzle_flash, "light_energy", 0.0, 0.05)
|
||||
|
||||
func _shoot_hitscan() -> void:
|
||||
if not camera: return
|
||||
|
||||
var space_state = camera.get_world_3d().direct_space_state
|
||||
var origin = camera.global_position
|
||||
var forward = -camera.global_transform.basis.z.normalized()
|
||||
var right = camera.global_transform.basis.x.normalized()
|
||||
var up = camera.global_transform.basis.y.normalized()
|
||||
|
||||
# Calculate spread
|
||||
var r = sqrt(randf()) * spread_angle
|
||||
var theta = randf() * PI * 2.0
|
||||
var offset_dir = (right * cos(theta) + up * sin(theta)) * r
|
||||
var ray_dir = (forward + offset_dir).normalized()
|
||||
|
||||
var target = origin + ray_dir * max_range
|
||||
var query = PhysicsRayQueryParameters3D.create(origin, target)
|
||||
|
||||
if player:
|
||||
query.exclude = [player.get_rid()]
|
||||
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result:
|
||||
var hit_dist = origin.distance_to(result.position)
|
||||
var damage = base_damage
|
||||
|
||||
if hit_dist > falloff_start:
|
||||
var falloff_factor = clampf((hit_dist - falloff_start) / (max_range - falloff_start), 0.0, 1.0)
|
||||
damage = lerpf(base_damage, min_damage, falloff_factor)
|
||||
|
||||
if result.collider.has_method("take_damage"):
|
||||
result.collider.take_damage(damage, result.position, player)
|
||||
Reference in New Issue
Block a user