feat: add plasma gun weapon implementation and new pause menu system with loadout management
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -261,4 +261,127 @@ for i in range(int(sample_rate * dj_duration)):
|
|||||||
dj_samples.append((filtered * 0.4 + lift) * env)
|
dj_samples.append((filtered * 0.4 + lift) * env)
|
||||||
write_wav('assets/sounds/double_jump.wav', dj_samples)
|
write_wav('assets/sounds/double_jump.wav', dj_samples)
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# NEW WEAPONS (AK47, M4, MP7, DMR, Plasma, Nail)
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
# 11. AK47 (Heavy punch, low thud, sharp crack)
|
||||||
|
ak_duration = 0.2
|
||||||
|
ak_samples = []
|
||||||
|
for i in range(int(sample_rate * ak_duration)):
|
||||||
|
t = i / sample_rate
|
||||||
|
noise = random.uniform(-1.0, 1.0)
|
||||||
|
|
||||||
|
# Sharp metallic crack
|
||||||
|
crack_env = math.exp(-t * 100.0)
|
||||||
|
crack = noise * crack_env * 0.4
|
||||||
|
|
||||||
|
# Deep heavy punch (70Hz sweep down to 40Hz)
|
||||||
|
punch_freq = max(40.0, 70.0 - t * 300.0)
|
||||||
|
punch_env = math.exp(-t * 20.0)
|
||||||
|
punch = math.sin(t * punch_freq * 2 * math.pi) * punch_env * 0.6
|
||||||
|
|
||||||
|
# Mechanism ring
|
||||||
|
ring_env = math.exp(-t * 15.0)
|
||||||
|
ring = math.sin(t * 800.0 * 2 * math.pi) * ring_env * 0.05
|
||||||
|
|
||||||
|
ak_samples.append(crack + punch + ring)
|
||||||
|
write_wav('assets/sounds/ak47_fire.wav', ak_samples)
|
||||||
|
|
||||||
|
|
||||||
|
# 12. M4 (Moderate punch, sharper crack, tighter burst)
|
||||||
|
m4_duration = 0.15
|
||||||
|
m4_samples = []
|
||||||
|
for i in range(int(sample_rate * m4_duration)):
|
||||||
|
t = i / sample_rate
|
||||||
|
noise = random.uniform(-1.0, 1.0)
|
||||||
|
|
||||||
|
crack_env = math.exp(-t * 120.0)
|
||||||
|
crack = noise * crack_env * 0.5
|
||||||
|
|
||||||
|
punch_freq = 90.0
|
||||||
|
punch_env = math.exp(-t * 25.0)
|
||||||
|
punch = math.sin(t * punch_freq * 2 * math.pi) * punch_env * 0.4
|
||||||
|
|
||||||
|
m4_samples.append(crack + punch)
|
||||||
|
write_wav('assets/sounds/m4_fire.wav', m4_samples)
|
||||||
|
|
||||||
|
|
||||||
|
# 13. MP7 (Very fast, light pop, high pitched)
|
||||||
|
mp7_duration = 0.1
|
||||||
|
mp7_samples = []
|
||||||
|
for i in range(int(sample_rate * mp7_duration)):
|
||||||
|
t = i / sample_rate
|
||||||
|
noise = random.uniform(-1.0, 1.0)
|
||||||
|
|
||||||
|
pop_env = math.exp(-t * 80.0)
|
||||||
|
pop = noise * pop_env * 0.3
|
||||||
|
|
||||||
|
punch_freq = 150.0
|
||||||
|
punch_env = math.exp(-t * 35.0)
|
||||||
|
punch = math.sin(t * punch_freq * 2 * math.pi) * punch_env * 0.2
|
||||||
|
|
||||||
|
mp7_samples.append(pop + punch)
|
||||||
|
write_wav('assets/sounds/mp7_fire.wav', mp7_samples)
|
||||||
|
|
||||||
|
|
||||||
|
# 14. DMR (Extremely loud, echoing boom, very sharp)
|
||||||
|
dmr_duration = 0.4
|
||||||
|
dmr_samples = []
|
||||||
|
for i in range(int(sample_rate * dmr_duration)):
|
||||||
|
t = i / sample_rate
|
||||||
|
noise = random.uniform(-1.0, 1.0)
|
||||||
|
|
||||||
|
crack_env = math.exp(-t * 50.0)
|
||||||
|
crack = noise * crack_env * 0.6
|
||||||
|
|
||||||
|
boom_freq = max(30.0, 60.0 - t * 100.0)
|
||||||
|
boom_env = math.exp(-t * 8.0)
|
||||||
|
boom = math.sin(t * boom_freq * 2 * math.pi) * boom_env * 0.7
|
||||||
|
|
||||||
|
tail_env = math.exp(-t * 5.0)
|
||||||
|
tail = noise * tail_env * 0.1 # outdoor echo reflection
|
||||||
|
|
||||||
|
dmr_samples.append(crack + boom + tail)
|
||||||
|
write_wav('assets/sounds/dmr_fire.wav', dmr_samples)
|
||||||
|
|
||||||
|
|
||||||
|
# 15. Plasma Gun (Sci-Fi pew, tonal sweep)
|
||||||
|
plasma_duration = 0.2
|
||||||
|
plasma_samples = []
|
||||||
|
for i in range(int(sample_rate * plasma_duration)):
|
||||||
|
t = i / sample_rate
|
||||||
|
|
||||||
|
# Sweep from 1200 Hz down to 300 Hz rapidly
|
||||||
|
freq = max(300.0, 1200.0 * math.exp(-t * 30.0))
|
||||||
|
pew_env = math.exp(-t * 15.0)
|
||||||
|
pew = math.sin(t * freq * 2 * math.pi) * pew_env * 0.6
|
||||||
|
|
||||||
|
plasma_samples.append(pew)
|
||||||
|
write_wav('assets/sounds/plasma_fire.wav', plasma_samples)
|
||||||
|
|
||||||
|
|
||||||
|
# 16. Nail Gun (Clack clack clack, metallic hit)
|
||||||
|
nail_duration = 0.1
|
||||||
|
nail_samples = []
|
||||||
|
for i in range(int(sample_rate * nail_duration)):
|
||||||
|
t = i / sample_rate
|
||||||
|
noise = random.uniform(-1.0, 1.0)
|
||||||
|
|
||||||
|
# Sharp metallic impact
|
||||||
|
hit_env = math.exp(-t * 150.0)
|
||||||
|
hit = noise * hit_env * 0.4
|
||||||
|
|
||||||
|
# Ringing metal (two tones)
|
||||||
|
ring_env = math.exp(-t * 40.0)
|
||||||
|
ring = (math.sin(t * 3500.0 * 2 * math.pi) + math.sin(t * 4100.0 * 2 * math.pi)) * ring_env * 0.15
|
||||||
|
|
||||||
|
# Mechanism chunk
|
||||||
|
chunk_env = math.exp(-t * 30.0)
|
||||||
|
chunk = math.sin(t * 200.0 * 2 * math.pi) * chunk_env * 0.2
|
||||||
|
|
||||||
|
nail_samples.append(hit + ring + chunk)
|
||||||
|
write_wav('assets/sounds/nailgun_fire.wav', nail_samples)
|
||||||
|
|
||||||
print("Generated all sounds!")
|
print("Generated all sounds!")
|
||||||
|
|||||||
@@ -12,6 +12,36 @@ var weapon_db = {
|
|||||||
"name": "Double Barrel Shotgun",
|
"name": "Double Barrel Shotgun",
|
||||||
"category": "primary",
|
"category": "primary",
|
||||||
"script": "res://weapons/double_barrel_shotgun.gd"
|
"script": "res://weapons/double_barrel_shotgun.gd"
|
||||||
|
},
|
||||||
|
"ak47": {
|
||||||
|
"name": "AK-47",
|
||||||
|
"category": "primary",
|
||||||
|
"script": "res://weapons/ak47.gd"
|
||||||
|
},
|
||||||
|
"m4": {
|
||||||
|
"name": "M4",
|
||||||
|
"category": "primary",
|
||||||
|
"script": "res://weapons/m4.gd"
|
||||||
|
},
|
||||||
|
"mp7": {
|
||||||
|
"name": "MP7",
|
||||||
|
"category": "primary",
|
||||||
|
"script": "res://weapons/mp7.gd"
|
||||||
|
},
|
||||||
|
"dmr": {
|
||||||
|
"name": "DMR",
|
||||||
|
"category": "primary",
|
||||||
|
"script": "res://weapons/dmr.gd"
|
||||||
|
},
|
||||||
|
"plasma_gun": {
|
||||||
|
"name": "Plasma Gun",
|
||||||
|
"category": "primary",
|
||||||
|
"script": "res://weapons/plasma_gun.gd"
|
||||||
|
},
|
||||||
|
"nail_gun": {
|
||||||
|
"name": "Nail Gun",
|
||||||
|
"category": "primary",
|
||||||
|
"script": "res://weapons/nail_gun.gd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ extends CanvasLayer
|
|||||||
var bg: ColorRect
|
var bg: ColorRect
|
||||||
var main_vbox: VBoxContainer
|
var main_vbox: VBoxContainer
|
||||||
var resume_btn: Button
|
var resume_btn: Button
|
||||||
|
var respawn_btn: Button
|
||||||
var loadouts_btn: Button
|
var loadouts_btn: Button
|
||||||
var settings_btn: Button
|
var settings_btn: Button
|
||||||
var exit_btn: Button
|
var exit_btn: Button
|
||||||
@@ -66,6 +67,10 @@ func _resume() -> void:
|
|||||||
get_tree().paused = false
|
get_tree().paused = false
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||||
|
|
||||||
|
func _respawn() -> void:
|
||||||
|
_resume() # Unpause
|
||||||
|
get_tree().reload_current_scene()
|
||||||
|
|
||||||
func _build_ui() -> void:
|
func _build_ui() -> void:
|
||||||
bg = ColorRect.new()
|
bg = ColorRect.new()
|
||||||
bg.color = Color(0, 0, 0, 0.7)
|
bg.color = Color(0, 0, 0, 0.7)
|
||||||
@@ -92,6 +97,11 @@ func _build_ui() -> void:
|
|||||||
resume_btn.custom_minimum_size = Vector2(200, 50)
|
resume_btn.custom_minimum_size = Vector2(200, 50)
|
||||||
main_vbox.add_child(resume_btn)
|
main_vbox.add_child(resume_btn)
|
||||||
|
|
||||||
|
respawn_btn = Button.new()
|
||||||
|
respawn_btn.text = "Respawn"
|
||||||
|
respawn_btn.custom_minimum_size = Vector2(200, 50)
|
||||||
|
main_vbox.add_child(respawn_btn)
|
||||||
|
|
||||||
loadouts_btn = Button.new()
|
loadouts_btn = Button.new()
|
||||||
loadouts_btn.text = "Loadouts"
|
loadouts_btn.text = "Loadouts"
|
||||||
loadouts_btn.custom_minimum_size = Vector2(200, 50)
|
loadouts_btn.custom_minimum_size = Vector2(200, 50)
|
||||||
@@ -250,6 +260,7 @@ func _create_label(text: String) -> Label:
|
|||||||
|
|
||||||
func _connect_signals() -> void:
|
func _connect_signals() -> void:
|
||||||
resume_btn.pressed.connect(_resume)
|
resume_btn.pressed.connect(_resume)
|
||||||
|
respawn_btn.pressed.connect(_respawn)
|
||||||
loadouts_btn.pressed.connect(_show_loadouts)
|
loadouts_btn.pressed.connect(_show_loadouts)
|
||||||
settings_btn.pressed.connect(_show_settings)
|
settings_btn.pressed.connect(_show_settings)
|
||||||
exit_btn.pressed.connect(_show_exit_dialog)
|
exit_btn.pressed.connect(_show_exit_dialog)
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
extends BaseHitscanWeapon
|
||||||
|
class_name AK47
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
weapon_name = "AK-47"
|
||||||
|
fire_rate = 0.15 # ~400 RPM
|
||||||
|
max_ammo = 30
|
||||||
|
reload_time = 2.0
|
||||||
|
base_damage = 35.0
|
||||||
|
min_damage = 25.0
|
||||||
|
falloff_start = 25.0
|
||||||
|
max_range = 150.0
|
||||||
|
automatic = true
|
||||||
|
spread_angle = 0.015
|
||||||
|
|
||||||
|
func _build_model() -> void:
|
||||||
|
# Position
|
||||||
|
position = Vector3(0.3, -0.3, -0.6)
|
||||||
|
|
||||||
|
# Receiver
|
||||||
|
var receiver = MeshInstance3D.new()
|
||||||
|
var rec_mesh = BoxMesh.new()
|
||||||
|
rec_mesh.size = Vector3(0.06, 0.12, 0.3)
|
||||||
|
var rec_mat = StandardMaterial3D.new()
|
||||||
|
rec_mat.albedo_color = Color(0.2, 0.2, 0.2)
|
||||||
|
rec_mesh.material = rec_mat
|
||||||
|
receiver.mesh = rec_mesh
|
||||||
|
receiver.position = Vector3(0, 0, 0.1)
|
||||||
|
model_root.add_child(receiver)
|
||||||
|
|
||||||
|
# Barrel
|
||||||
|
var barrel = MeshInstance3D.new()
|
||||||
|
var b_mesh = CylinderMesh.new()
|
||||||
|
b_mesh.top_radius = 0.015
|
||||||
|
b_mesh.bottom_radius = 0.015
|
||||||
|
b_mesh.height = 0.45
|
||||||
|
b_mesh.material = rec_mat
|
||||||
|
barrel.mesh = b_mesh
|
||||||
|
barrel.rotation.x = deg_to_rad(90)
|
||||||
|
barrel.position = Vector3(0, 0.03, -0.2)
|
||||||
|
model_root.add_child(barrel)
|
||||||
|
|
||||||
|
# Wood Handguard
|
||||||
|
var guard = MeshInstance3D.new()
|
||||||
|
var g_mesh = BoxMesh.new()
|
||||||
|
g_mesh.size = Vector3(0.08, 0.08, 0.2)
|
||||||
|
var wood_mat = StandardMaterial3D.new()
|
||||||
|
wood_mat.albedo_color = Color(0.5, 0.25, 0.1)
|
||||||
|
g_mesh.material = wood_mat
|
||||||
|
guard.mesh = g_mesh
|
||||||
|
guard.position = Vector3(0, 0, -0.1)
|
||||||
|
model_root.add_child(guard)
|
||||||
|
|
||||||
|
# Wood Stock
|
||||||
|
var stock = MeshInstance3D.new()
|
||||||
|
var s_mesh = BoxMesh.new()
|
||||||
|
s_mesh.size = Vector3(0.06, 0.1, 0.25)
|
||||||
|
s_mesh.material = wood_mat
|
||||||
|
stock.mesh = s_mesh
|
||||||
|
stock.position = Vector3(0, -0.02, 0.35)
|
||||||
|
model_root.add_child(stock)
|
||||||
|
|
||||||
|
# Muzzle Flash
|
||||||
|
muzzle_flash = OmniLight3D.new()
|
||||||
|
muzzle_flash.light_color = Color(1.0, 0.7, 0.2)
|
||||||
|
muzzle_flash.light_energy = 0.0
|
||||||
|
muzzle_flash.omni_range = 5.0
|
||||||
|
muzzle_flash.position = Vector3(0, 0.03, -0.45)
|
||||||
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
|
fire_sound.stream = load("res://assets/sounds/ak47_fire.wav")
|
||||||
|
fire_sound.position = muzzle_flash.position
|
||||||
|
fire_sound.volume_db = -5.0
|
||||||
|
model_root.add_child(fire_sound)
|
||||||
|
|
||||||
|
reload_sound = AudioStreamPlayer3D.new()
|
||||||
|
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # generic reload
|
||||||
|
model_root.add_child(reload_sound)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bddm0nooqfj2
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://l06bdsfbd0up
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
extends Node3D
|
||||||
|
class_name BaseProjectileWeapon
|
||||||
|
|
||||||
|
@export var weapon_name: String = "Base Projectile"
|
||||||
|
@export var fire_rate: float = 0.1
|
||||||
|
@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 = 200.0
|
||||||
|
@export var automatic: bool = true
|
||||||
|
@export var spread_angle: float = 0.01
|
||||||
|
|
||||||
|
@export var projectile_speed: float = 50.0
|
||||||
|
@export var projectile_gravity: float = 0.0
|
||||||
|
|
||||||
|
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:
|
||||||
|
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_projectile()
|
||||||
|
_play_muzzle_flash()
|
||||||
|
|
||||||
|
if fire_sound:
|
||||||
|
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_projectile() -> void:
|
||||||
|
if not camera: return
|
||||||
|
|
||||||
|
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 fire_dir = (forward + offset_dir).normalized()
|
||||||
|
|
||||||
|
_spawn_custom_projectile(origin, fire_dir)
|
||||||
|
|
||||||
|
func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||||
|
# Overridden by specific weapons to instance their unique visual projectile
|
||||||
|
pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cbkr1yr1xtfum
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
extends BaseHitscanWeapon
|
||||||
|
class_name DMR
|
||||||
|
|
||||||
|
var is_ads: bool = false
|
||||||
|
var default_fov: float = 90.0
|
||||||
|
var ads_fov: float = 40.0
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
weapon_name = "DMR"
|
||||||
|
fire_rate = 0.3 # Cooldown to prevent autoclicker spam
|
||||||
|
max_ammo = 15
|
||||||
|
reload_time = 2.2
|
||||||
|
base_damage = 75.0
|
||||||
|
min_damage = 60.0
|
||||||
|
falloff_start = 50.0
|
||||||
|
max_range = 300.0
|
||||||
|
automatic = false
|
||||||
|
spread_angle = 0.002 # Extremely accurate
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
if camera:
|
||||||
|
default_fov = camera.fov
|
||||||
|
|
||||||
|
func _build_model() -> void:
|
||||||
|
position = Vector3(0.3, -0.3, -0.7)
|
||||||
|
|
||||||
|
var body_mat = StandardMaterial3D.new()
|
||||||
|
body_mat.albedo_color = Color(0.15, 0.25, 0.15) # Olive drab
|
||||||
|
var black_mat = StandardMaterial3D.new()
|
||||||
|
black_mat.albedo_color = Color(0.05, 0.05, 0.05)
|
||||||
|
|
||||||
|
# Receiver
|
||||||
|
var receiver = MeshInstance3D.new()
|
||||||
|
var rec_mesh = BoxMesh.new()
|
||||||
|
rec_mesh.size = Vector3(0.05, 0.12, 0.4)
|
||||||
|
rec_mesh.material = body_mat
|
||||||
|
receiver.mesh = rec_mesh
|
||||||
|
receiver.position = Vector3(0, 0, 0.1)
|
||||||
|
model_root.add_child(receiver)
|
||||||
|
|
||||||
|
# Barrel
|
||||||
|
var barrel = MeshInstance3D.new()
|
||||||
|
var b_mesh = CylinderMesh.new()
|
||||||
|
b_mesh.top_radius = 0.01
|
||||||
|
b_mesh.bottom_radius = 0.01
|
||||||
|
b_mesh.height = 0.6
|
||||||
|
b_mesh.material = black_mat
|
||||||
|
barrel.mesh = b_mesh
|
||||||
|
barrel.rotation.x = deg_to_rad(90)
|
||||||
|
barrel.position = Vector3(0, 0.03, -0.3)
|
||||||
|
model_root.add_child(barrel)
|
||||||
|
|
||||||
|
# Scope
|
||||||
|
var scope = MeshInstance3D.new()
|
||||||
|
var scope_mesh = CylinderMesh.new()
|
||||||
|
scope_mesh.top_radius = 0.02
|
||||||
|
scope_mesh.bottom_radius = 0.02
|
||||||
|
scope_mesh.height = 0.2
|
||||||
|
scope_mesh.material = black_mat
|
||||||
|
scope.mesh = scope_mesh
|
||||||
|
scope.rotation.x = deg_to_rad(90)
|
||||||
|
scope.position = Vector3(0, 0.08, 0.05)
|
||||||
|
model_root.add_child(scope)
|
||||||
|
|
||||||
|
# Muzzle Flash
|
||||||
|
muzzle_flash = OmniLight3D.new()
|
||||||
|
muzzle_flash.light_color = Color(1.0, 0.8, 0.4)
|
||||||
|
muzzle_flash.light_energy = 0.0
|
||||||
|
muzzle_flash.omni_range = 6.0
|
||||||
|
muzzle_flash.position = Vector3(0, 0.03, -0.6)
|
||||||
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
|
fire_sound.stream = load("res://assets/sounds/dmr_fire.wav")
|
||||||
|
fire_sound.position = muzzle_flash.position
|
||||||
|
fire_sound.volume_db = -3.0
|
||||||
|
model_root.add_child(fire_sound)
|
||||||
|
|
||||||
|
reload_sound = AudioStreamPlayer3D.new()
|
||||||
|
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
|
||||||
|
model_root.add_child(reload_sound)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
super._process(delta)
|
||||||
|
|
||||||
|
if camera:
|
||||||
|
var target_fov = ads_fov if is_ads else default_fov
|
||||||
|
camera.fov = lerpf(camera.fov, target_fov, 15.0 * delta)
|
||||||
|
|
||||||
|
# Move model to center when ADS
|
||||||
|
var target_pos = Vector3(0.0, -0.2, -0.5) if is_ads else Vector3(0.3, -0.3, -0.7)
|
||||||
|
position = position.lerp(target_pos, 15.0 * delta)
|
||||||
|
|
||||||
|
# Adjust sensitivity if player has a way to modify it (we can modify the camera rig later)
|
||||||
|
# For now, FOV alone gives the zoom effect.
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
super._input(event)
|
||||||
|
|
||||||
|
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||||
|
return
|
||||||
|
|
||||||
|
if event.is_action_pressed("fire_alt"):
|
||||||
|
is_ads = true
|
||||||
|
elif event.is_action_released("fire_alt"):
|
||||||
|
is_ads = false
|
||||||
|
|
||||||
|
func _start_reload() -> void:
|
||||||
|
super._start_reload()
|
||||||
|
is_ads = false # Drop out of ADS on reload
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dy2xsq04e6ryt
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
extends BaseHitscanWeapon
|
||||||
|
class_name M4
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
weapon_name = "M4"
|
||||||
|
fire_rate = 0.08 # ~750 RPM
|
||||||
|
max_ammo = 40
|
||||||
|
reload_time = 1.6
|
||||||
|
base_damage = 22.0
|
||||||
|
min_damage = 12.0
|
||||||
|
falloff_start = 15.0
|
||||||
|
max_range = 100.0
|
||||||
|
automatic = true
|
||||||
|
spread_angle = 0.01
|
||||||
|
|
||||||
|
func _build_model() -> void:
|
||||||
|
position = Vector3(0.3, -0.3, -0.6)
|
||||||
|
|
||||||
|
var grey_mat = StandardMaterial3D.new()
|
||||||
|
grey_mat.albedo_color = Color(0.25, 0.25, 0.25)
|
||||||
|
var dark_mat = StandardMaterial3D.new()
|
||||||
|
dark_mat.albedo_color = Color(0.1, 0.1, 0.1)
|
||||||
|
|
||||||
|
# Receiver
|
||||||
|
var receiver = MeshInstance3D.new()
|
||||||
|
var rec_mesh = BoxMesh.new()
|
||||||
|
rec_mesh.size = Vector3(0.05, 0.14, 0.25)
|
||||||
|
rec_mesh.material = grey_mat
|
||||||
|
receiver.mesh = rec_mesh
|
||||||
|
receiver.position = Vector3(0, 0, 0.1)
|
||||||
|
model_root.add_child(receiver)
|
||||||
|
|
||||||
|
# Barrel
|
||||||
|
var barrel = MeshInstance3D.new()
|
||||||
|
var b_mesh = CylinderMesh.new()
|
||||||
|
b_mesh.top_radius = 0.012
|
||||||
|
b_mesh.bottom_radius = 0.012
|
||||||
|
b_mesh.height = 0.4
|
||||||
|
b_mesh.material = dark_mat
|
||||||
|
barrel.mesh = b_mesh
|
||||||
|
barrel.rotation.x = deg_to_rad(90)
|
||||||
|
barrel.position = Vector3(0, 0.04, -0.2)
|
||||||
|
model_root.add_child(barrel)
|
||||||
|
|
||||||
|
# Handguard
|
||||||
|
var guard = MeshInstance3D.new()
|
||||||
|
var g_mesh = BoxMesh.new()
|
||||||
|
g_mesh.size = Vector3(0.06, 0.07, 0.22)
|
||||||
|
g_mesh.material = dark_mat
|
||||||
|
guard.mesh = g_mesh
|
||||||
|
guard.position = Vector3(0, 0.04, -0.1)
|
||||||
|
model_root.add_child(guard)
|
||||||
|
|
||||||
|
# Muzzle Flash
|
||||||
|
muzzle_flash = OmniLight3D.new()
|
||||||
|
muzzle_flash.light_color = Color(1.0, 0.8, 0.4)
|
||||||
|
muzzle_flash.light_energy = 0.0
|
||||||
|
muzzle_flash.omni_range = 4.0
|
||||||
|
muzzle_flash.position = Vector3(0, 0.04, -0.4)
|
||||||
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
|
fire_sound.stream = load("res://assets/sounds/m4_fire.wav")
|
||||||
|
fire_sound.position = muzzle_flash.position
|
||||||
|
fire_sound.volume_db = -8.0
|
||||||
|
model_root.add_child(fire_sound)
|
||||||
|
|
||||||
|
reload_sound = AudioStreamPlayer3D.new()
|
||||||
|
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
|
||||||
|
model_root.add_child(reload_sound)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bctti6s7em04f
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
extends BaseHitscanWeapon
|
||||||
|
class_name MP7
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
weapon_name = "MP7"
|
||||||
|
fire_rate = 0.05 # ~1200 RPM
|
||||||
|
max_ammo = 45
|
||||||
|
reload_time = 1.2
|
||||||
|
base_damage = 12.0
|
||||||
|
min_damage = 3.0
|
||||||
|
falloff_start = 8.0
|
||||||
|
max_range = 50.0
|
||||||
|
automatic = true
|
||||||
|
spread_angle = 0.025
|
||||||
|
|
||||||
|
func _build_model() -> void:
|
||||||
|
position = Vector3(0.25, -0.25, -0.5)
|
||||||
|
|
||||||
|
var black_mat = StandardMaterial3D.new()
|
||||||
|
black_mat.albedo_color = Color(0.05, 0.05, 0.05)
|
||||||
|
|
||||||
|
# Receiver
|
||||||
|
var receiver = MeshInstance3D.new()
|
||||||
|
var rec_mesh = BoxMesh.new()
|
||||||
|
rec_mesh.size = Vector3(0.04, 0.1, 0.2)
|
||||||
|
rec_mesh.material = black_mat
|
||||||
|
receiver.mesh = rec_mesh
|
||||||
|
receiver.position = Vector3(0, 0, 0)
|
||||||
|
model_root.add_child(receiver)
|
||||||
|
|
||||||
|
# Grip
|
||||||
|
var grip = MeshInstance3D.new()
|
||||||
|
var g_mesh = BoxMesh.new()
|
||||||
|
g_mesh.size = Vector3(0.03, 0.12, 0.04)
|
||||||
|
g_mesh.material = black_mat
|
||||||
|
grip.mesh = g_mesh
|
||||||
|
grip.position = Vector3(0, -0.06, 0.08)
|
||||||
|
grip.rotation.x = deg_to_rad(10)
|
||||||
|
model_root.add_child(grip)
|
||||||
|
|
||||||
|
# Foregrip
|
||||||
|
var fgrip = MeshInstance3D.new()
|
||||||
|
var fg_mesh = BoxMesh.new()
|
||||||
|
fg_mesh.size = Vector3(0.03, 0.08, 0.03)
|
||||||
|
fg_mesh.material = black_mat
|
||||||
|
fgrip.mesh = fg_mesh
|
||||||
|
fgrip.position = Vector3(0, -0.05, -0.06)
|
||||||
|
fgrip.rotation.x = deg_to_rad(-10)
|
||||||
|
model_root.add_child(fgrip)
|
||||||
|
|
||||||
|
# Barrel
|
||||||
|
var barrel = MeshInstance3D.new()
|
||||||
|
var b_mesh = CylinderMesh.new()
|
||||||
|
b_mesh.top_radius = 0.008
|
||||||
|
b_mesh.bottom_radius = 0.008
|
||||||
|
b_mesh.height = 0.1
|
||||||
|
b_mesh.material = black_mat
|
||||||
|
barrel.mesh = b_mesh
|
||||||
|
barrel.rotation.x = deg_to_rad(90)
|
||||||
|
barrel.position = Vector3(0, 0.02, -0.15)
|
||||||
|
model_root.add_child(barrel)
|
||||||
|
|
||||||
|
# Muzzle Flash
|
||||||
|
muzzle_flash = OmniLight3D.new()
|
||||||
|
muzzle_flash.light_color = Color(1.0, 0.9, 0.5)
|
||||||
|
muzzle_flash.light_energy = 0.0
|
||||||
|
muzzle_flash.omni_range = 3.0
|
||||||
|
muzzle_flash.position = Vector3(0, 0.02, -0.2)
|
||||||
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
|
fire_sound.stream = load("res://assets/sounds/mp7_fire.wav")
|
||||||
|
fire_sound.position = muzzle_flash.position
|
||||||
|
fire_sound.volume_db = -10.0
|
||||||
|
model_root.add_child(fire_sound)
|
||||||
|
|
||||||
|
reload_sound = AudioStreamPlayer3D.new()
|
||||||
|
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
|
||||||
|
model_root.add_child(reload_sound)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://unto0q3j6jix
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
extends BaseProjectileWeapon
|
||||||
|
class_name NailGun
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
weapon_name = "Nail Gun"
|
||||||
|
fire_rate = 0.06 # Very fast (1000 RPM)
|
||||||
|
max_ammo = 60
|
||||||
|
reload_time = 2.0
|
||||||
|
base_damage = 18.0
|
||||||
|
min_damage = 10.0
|
||||||
|
falloff_start = 20.0
|
||||||
|
max_range = 100.0
|
||||||
|
automatic = true
|
||||||
|
spread_angle = 0.02
|
||||||
|
|
||||||
|
projectile_speed = 60.0 # Fast but requires lead
|
||||||
|
projectile_gravity = 5.0 # Slight drop
|
||||||
|
|
||||||
|
func _build_model() -> void:
|
||||||
|
position = Vector3(0.3, -0.3, -0.6)
|
||||||
|
|
||||||
|
var yellow_mat = StandardMaterial3D.new()
|
||||||
|
yellow_mat.albedo_color = Color(0.8, 0.7, 0.1) # Construction yellow
|
||||||
|
var dark_mat = StandardMaterial3D.new()
|
||||||
|
dark_mat.albedo_color = Color(0.2, 0.2, 0.2)
|
||||||
|
|
||||||
|
# Body
|
||||||
|
var body = MeshInstance3D.new()
|
||||||
|
var b_mesh = BoxMesh.new()
|
||||||
|
b_mesh.size = Vector3(0.06, 0.16, 0.25)
|
||||||
|
b_mesh.material = yellow_mat
|
||||||
|
body.mesh = b_mesh
|
||||||
|
body.position = Vector3(0, 0.02, 0)
|
||||||
|
model_root.add_child(body)
|
||||||
|
|
||||||
|
# Handle
|
||||||
|
var handle = MeshInstance3D.new()
|
||||||
|
var h_mesh = BoxMesh.new()
|
||||||
|
h_mesh.size = Vector3(0.04, 0.1, 0.04)
|
||||||
|
h_mesh.material = dark_mat
|
||||||
|
handle.mesh = h_mesh
|
||||||
|
handle.position = Vector3(0, -0.1, 0.05)
|
||||||
|
model_root.add_child(handle)
|
||||||
|
|
||||||
|
# Mag (Nail Drum)
|
||||||
|
var drum = MeshInstance3D.new()
|
||||||
|
var d_mesh = CylinderMesh.new()
|
||||||
|
d_mesh.top_radius = 0.05
|
||||||
|
d_mesh.bottom_radius = 0.05
|
||||||
|
d_mesh.height = 0.1
|
||||||
|
d_mesh.material = dark_mat
|
||||||
|
drum.mesh = d_mesh
|
||||||
|
drum.rotation.z = deg_to_rad(90)
|
||||||
|
drum.position = Vector3(0, -0.06, -0.05)
|
||||||
|
model_root.add_child(drum)
|
||||||
|
|
||||||
|
# Barrel
|
||||||
|
var barrel = MeshInstance3D.new()
|
||||||
|
var bar_mesh = CylinderMesh.new()
|
||||||
|
bar_mesh.top_radius = 0.01
|
||||||
|
bar_mesh.bottom_radius = 0.01
|
||||||
|
bar_mesh.height = 0.15
|
||||||
|
bar_mesh.material = dark_mat
|
||||||
|
barrel.mesh = bar_mesh
|
||||||
|
barrel.rotation.x = deg_to_rad(90)
|
||||||
|
barrel.position = Vector3(0, 0.05, -0.2)
|
||||||
|
model_root.add_child(barrel)
|
||||||
|
|
||||||
|
# Muzzle Flash (Sparks)
|
||||||
|
muzzle_flash = OmniLight3D.new()
|
||||||
|
muzzle_flash.light_color = Color(1.0, 0.9, 0.6)
|
||||||
|
muzzle_flash.light_energy = 0.0
|
||||||
|
muzzle_flash.omni_range = 3.0
|
||||||
|
muzzle_flash.position = Vector3(0, 0.05, -0.3)
|
||||||
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
|
fire_sound.stream = load("res://assets/sounds/nailgun_fire.wav")
|
||||||
|
fire_sound.position = muzzle_flash.position
|
||||||
|
fire_sound.volume_db = -5.0
|
||||||
|
model_root.add_child(fire_sound)
|
||||||
|
|
||||||
|
reload_sound = AudioStreamPlayer3D.new()
|
||||||
|
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
|
||||||
|
model_root.add_child(reload_sound)
|
||||||
|
|
||||||
|
func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||||
|
var proj = load("res://weapons/projectile.gd").new()
|
||||||
|
|
||||||
|
# Give it a nail/spike mesh
|
||||||
|
var mesh_instance = MeshInstance3D.new()
|
||||||
|
var p_mesh = CylinderMesh.new()
|
||||||
|
p_mesh.top_radius = 0.0
|
||||||
|
p_mesh.bottom_radius = 0.01
|
||||||
|
p_mesh.height = 0.1
|
||||||
|
var mat = StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = Color(0.7, 0.7, 0.7) # Shiny metal
|
||||||
|
mat.metallic = 1.0
|
||||||
|
mat.roughness = 0.3
|
||||||
|
p_mesh.material = mat
|
||||||
|
mesh_instance.mesh = p_mesh
|
||||||
|
# Rotate it so it points forward
|
||||||
|
mesh_instance.rotation.x = deg_to_rad(-90)
|
||||||
|
proj.add_child(mesh_instance)
|
||||||
|
|
||||||
|
# Projectile rotation so it faces velocity
|
||||||
|
# (Basis looking towards fire_dir)
|
||||||
|
var up = Vector3.UP
|
||||||
|
if abs(fire_dir.dot(up)) > 0.99:
|
||||||
|
up = Vector3.RIGHT
|
||||||
|
proj.transform.basis = Basis.looking_at(fire_dir, up)
|
||||||
|
|
||||||
|
proj.global_position = muzzle_flash.global_position if muzzle_flash else origin
|
||||||
|
proj.velocity = fire_dir * projectile_speed
|
||||||
|
proj.damage = base_damage
|
||||||
|
proj.min_damage = min_damage
|
||||||
|
proj.falloff_start = falloff_start
|
||||||
|
proj.max_range = max_range
|
||||||
|
proj.drop_gravity = projectile_gravity
|
||||||
|
proj.owner_player = player
|
||||||
|
|
||||||
|
# Spawn in the world
|
||||||
|
get_tree().current_scene.add_child(proj)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bkl7mdau5363
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
extends BaseProjectileWeapon
|
||||||
|
class_name PlasmaGun
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
weapon_name = "Plasma Gun"
|
||||||
|
fire_rate = 0.15 # Moderate
|
||||||
|
max_ammo = 25
|
||||||
|
reload_time = 1.8
|
||||||
|
base_damage = 50.0
|
||||||
|
min_damage = 50.0 # No falloff
|
||||||
|
falloff_start = 0.0 # Unused because min=base
|
||||||
|
max_range = 300.0
|
||||||
|
automatic = true
|
||||||
|
spread_angle = 0.005 # Highly accurate
|
||||||
|
|
||||||
|
projectile_speed = 80.0
|
||||||
|
projectile_gravity = 0.0
|
||||||
|
|
||||||
|
func _build_model() -> void:
|
||||||
|
position = Vector3(0.3, -0.3, -0.6)
|
||||||
|
|
||||||
|
var white_mat = StandardMaterial3D.new()
|
||||||
|
white_mat.albedo_color = Color(0.8, 0.8, 0.9)
|
||||||
|
var glow_mat = StandardMaterial3D.new()
|
||||||
|
glow_mat.albedo_color = Color(0.2, 0.5, 1.0)
|
||||||
|
glow_mat.emission_enabled = true
|
||||||
|
glow_mat.emission = Color(0.2, 0.5, 1.0)
|
||||||
|
glow_mat.emission_energy_multiplier = 4.0
|
||||||
|
|
||||||
|
# Body
|
||||||
|
var body = MeshInstance3D.new()
|
||||||
|
var b_mesh = BoxMesh.new()
|
||||||
|
b_mesh.size = Vector3(0.08, 0.14, 0.35)
|
||||||
|
b_mesh.material = white_mat
|
||||||
|
body.mesh = b_mesh
|
||||||
|
body.position = Vector3(0, 0, 0.05)
|
||||||
|
model_root.add_child(body)
|
||||||
|
|
||||||
|
# Glow Core
|
||||||
|
var core = MeshInstance3D.new()
|
||||||
|
var c_mesh = CylinderMesh.new()
|
||||||
|
c_mesh.top_radius = 0.02
|
||||||
|
c_mesh.bottom_radius = 0.02
|
||||||
|
c_mesh.height = 0.4
|
||||||
|
c_mesh.material = glow_mat
|
||||||
|
core.mesh = c_mesh
|
||||||
|
core.rotation.x = deg_to_rad(90)
|
||||||
|
core.position = Vector3(0, 0.02, -0.15)
|
||||||
|
model_root.add_child(core)
|
||||||
|
|
||||||
|
# Muzzle Flash
|
||||||
|
muzzle_flash = OmniLight3D.new()
|
||||||
|
muzzle_flash.light_color = Color(0.2, 0.5, 1.0)
|
||||||
|
muzzle_flash.light_energy = 0.0
|
||||||
|
muzzle_flash.omni_range = 6.0
|
||||||
|
muzzle_flash.position = Vector3(0, 0.02, -0.4)
|
||||||
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
|
fire_sound.stream = load("res://assets/sounds/plasma_fire.wav")
|
||||||
|
fire_sound.position = muzzle_flash.position
|
||||||
|
fire_sound.volume_db = -5.0
|
||||||
|
model_root.add_child(fire_sound)
|
||||||
|
|
||||||
|
reload_sound = AudioStreamPlayer3D.new()
|
||||||
|
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
|
||||||
|
model_root.add_child(reload_sound)
|
||||||
|
|
||||||
|
func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||||
|
var proj = load("res://weapons/projectile.gd").new()
|
||||||
|
|
||||||
|
# Give it a glowing blue mesh
|
||||||
|
var mesh_instance = MeshInstance3D.new()
|
||||||
|
var p_mesh = SphereMesh.new()
|
||||||
|
p_mesh.radius = 0.08
|
||||||
|
p_mesh.height = 0.16
|
||||||
|
var mat = StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = Color(0.2, 0.6, 1.0)
|
||||||
|
mat.emission_enabled = true
|
||||||
|
mat.emission = Color(0.2, 0.6, 1.0)
|
||||||
|
mat.emission_energy_multiplier = 5.0
|
||||||
|
p_mesh.material = mat
|
||||||
|
mesh_instance.mesh = p_mesh
|
||||||
|
proj.add_child(mesh_instance)
|
||||||
|
|
||||||
|
var light = OmniLight3D.new()
|
||||||
|
light.light_color = Color(0.2, 0.6, 1.0)
|
||||||
|
light.light_energy = 2.0
|
||||||
|
light.omni_range = 4.0
|
||||||
|
proj.add_child(light)
|
||||||
|
|
||||||
|
proj.global_position = muzzle_flash.global_position if muzzle_flash else origin
|
||||||
|
proj.velocity = fire_dir * projectile_speed
|
||||||
|
proj.damage = base_damage
|
||||||
|
proj.min_damage = min_damage
|
||||||
|
proj.falloff_start = falloff_start
|
||||||
|
proj.max_range = max_range
|
||||||
|
proj.drop_gravity = projectile_gravity
|
||||||
|
proj.owner_player = player
|
||||||
|
|
||||||
|
# Spawn in the world
|
||||||
|
get_tree().current_scene.add_child(proj)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://n156sg54wvpp
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
extends Node3D
|
||||||
|
class_name Projectile
|
||||||
|
|
||||||
|
var velocity: Vector3 = Vector3.ZERO
|
||||||
|
var damage: float = 10.0
|
||||||
|
var max_range: float = 200.0
|
||||||
|
var distance_traveled: float = 0.0
|
||||||
|
var owner_player: CharacterBody3D
|
||||||
|
var falloff_start: float = 10.0
|
||||||
|
var min_damage: float = 5.0
|
||||||
|
var drop_gravity: float = 0.0 # For arc projectiles (nailgun might have slight drop, plasma none)
|
||||||
|
|
||||||
|
var _previous_position: Vector3
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_previous_position = global_position
|
||||||
|
# Automatically destroy after 5 seconds to prevent leaks
|
||||||
|
var t = get_tree().create_timer(5.0)
|
||||||
|
t.timeout.connect(queue_free)
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
# Apply gravity drop if any
|
||||||
|
velocity.y -= drop_gravity * delta
|
||||||
|
|
||||||
|
var movement = velocity * delta
|
||||||
|
global_position += movement
|
||||||
|
|
||||||
|
distance_traveled += movement.length()
|
||||||
|
|
||||||
|
if distance_traveled > max_range:
|
||||||
|
queue_free()
|
||||||
|
return
|
||||||
|
|
||||||
|
_check_collision()
|
||||||
|
_previous_position = global_position
|
||||||
|
|
||||||
|
func _check_collision() -> void:
|
||||||
|
var space_state = get_world_3d().direct_space_state
|
||||||
|
var query = PhysicsRayQueryParameters3D.create(_previous_position, global_position)
|
||||||
|
if owner_player:
|
||||||
|
query.exclude = [owner_player.get_rid()]
|
||||||
|
|
||||||
|
var result = space_state.intersect_ray(query)
|
||||||
|
if result:
|
||||||
|
_on_hit(result)
|
||||||
|
|
||||||
|
func _on_hit(result: Dictionary) -> void:
|
||||||
|
var hit_dist = distance_traveled - (_previous_position.distance_to(result.position))
|
||||||
|
|
||||||
|
var final_damage = damage
|
||||||
|
if hit_dist > falloff_start and falloff_start > 0.0:
|
||||||
|
var falloff_factor = clampf((hit_dist - falloff_start) / (max_range - falloff_start), 0.0, 1.0)
|
||||||
|
final_damage = lerpf(damage, min_damage, falloff_factor)
|
||||||
|
|
||||||
|
if result.collider.has_method("take_damage"):
|
||||||
|
result.collider.take_damage(final_damage, result.position, owner_player)
|
||||||
|
|
||||||
|
# Spawn impact effect? Not needed yet for prototype.
|
||||||
|
queue_free()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://davfge7rifxls
|
||||||
@@ -55,6 +55,10 @@ func _equip_slot(slot: int) -> void:
|
|||||||
var w = weapons[active_slot]
|
var w = weapons[active_slot]
|
||||||
w.visible = false
|
w.visible = false
|
||||||
w.set_process_input(false)
|
w.set_process_input(false)
|
||||||
|
if "is_firing" in w:
|
||||||
|
w.is_firing = false
|
||||||
|
if w.has_method("unequip"):
|
||||||
|
w.unequip()
|
||||||
|
|
||||||
active_slot = slot
|
active_slot = slot
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user