feat: implement weapon system, loadout manager, and specialized projectile behaviors with associated assets
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
extends BaseProjectileWeapon
|
||||
class_name RocketSwarm
|
||||
|
||||
var is_targeting: bool = false
|
||||
var homing_target_pos: Vector3 = Vector3.ZERO
|
||||
var targeting_laser: MeshInstance3D
|
||||
|
||||
func _init() -> void:
|
||||
weapon_name = "Rocket Swarm"
|
||||
fire_rate = 1.0 # 1 click fires all 8
|
||||
max_ammo = 4 # 4 swarms
|
||||
reload_time = 3.0
|
||||
base_damage = 20.0 # Per mini rocket
|
||||
min_damage = 20.0
|
||||
falloff_start = 0.0
|
||||
max_range = 300.0
|
||||
automatic = false
|
||||
spread_angle = 0.15 # Wide spread out of the barrel
|
||||
|
||||
projectile_speed = 25.0 # Slower so they have time to home
|
||||
|
||||
func _build_model() -> void:
|
||||
position = Vector3(0.4, -0.4, -0.6)
|
||||
|
||||
var mat_box = StandardMaterial3D.new()
|
||||
mat_box.albedo_color = Color(0.2, 0.4, 0.5)
|
||||
|
||||
# Box launcher
|
||||
var body = MeshInstance3D.new()
|
||||
var b_mesh = BoxMesh.new()
|
||||
b_mesh.size = Vector3(0.2, 0.2, 0.4)
|
||||
b_mesh.material = mat_box
|
||||
body.mesh = b_mesh
|
||||
model_root.add_child(body)
|
||||
|
||||
# Laser
|
||||
targeting_laser = MeshInstance3D.new()
|
||||
var l_mesh = CylinderMesh.new()
|
||||
l_mesh.top_radius = 0.02 # Make it thicker and more visible
|
||||
l_mesh.bottom_radius = 0.02
|
||||
l_mesh.height = 300.0
|
||||
var l_mat = StandardMaterial3D.new()
|
||||
l_mat.albedo_color = Color(1.0, 0.0, 0.0, 1.0)
|
||||
l_mat.emission_enabled = true
|
||||
l_mat.emission = Color(2.0, 0.0, 0.0) # Bright red
|
||||
l_mesh.material = l_mat
|
||||
targeting_laser.mesh = l_mesh
|
||||
targeting_laser.rotation.x = deg_to_rad(90)
|
||||
targeting_laser.position = Vector3(0, 0, -150.0)
|
||||
targeting_laser.visible = false
|
||||
model_root.add_child(targeting_laser)
|
||||
|
||||
# Audio
|
||||
fire_sound = AudioStreamPlayer3D.new()
|
||||
fire_sound.stream = load("res://assets/sounds/swarm_fire.wav")
|
||||
model_root.add_child(fire_sound)
|
||||
|
||||
reload_sound = AudioStreamPlayer3D.new()
|
||||
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing
|
||||
model_root.add_child(reload_sound)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
super._process(delta)
|
||||
|
||||
if camera:
|
||||
is_targeting = Input.is_action_pressed("fire_alt")
|
||||
targeting_laser.visible = is_targeting
|
||||
|
||||
if is_targeting:
|
||||
var space_state = get_world_3d().direct_space_state
|
||||
var origin = camera.global_position
|
||||
var target = origin - camera.global_transform.basis.z * 300.0
|
||||
var query = PhysicsRayQueryParameters3D.create(origin, target)
|
||||
if player:
|
||||
query.exclude = [player.get_rid()]
|
||||
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result:
|
||||
homing_target_pos = result.position
|
||||
# Shrink laser to hit point
|
||||
var dist = origin.distance_to(result.position)
|
||||
(targeting_laser.mesh as CylinderMesh).height = dist
|
||||
targeting_laser.position.z = -dist / 2.0
|
||||
else:
|
||||
homing_target_pos = target
|
||||
(targeting_laser.mesh as CylinderMesh).height = 300.0
|
||||
targeting_laser.position.z = -150.0
|
||||
|
||||
func unequip() -> void:
|
||||
is_targeting = false
|
||||
targeting_laser.visible = false
|
||||
|
||||
# Override fire to shoot 8 rockets at once
|
||||
func _fire() -> void:
|
||||
if current_ammo <= 0 and not reloading:
|
||||
_start_reload()
|
||||
return
|
||||
|
||||
if not camera: return
|
||||
|
||||
current_ammo -= 1
|
||||
fire_cooldown = fire_rate
|
||||
|
||||
if fire_sound:
|
||||
fire_sound.play()
|
||||
|
||||
if muzzle_flash:
|
||||
muzzle_flash.light_energy = 2.0
|
||||
var tween = create_tween()
|
||||
tween.tween_property(muzzle_flash, "light_energy", 0.0, 0.1)
|
||||
|
||||
var origin = camera.global_position
|
||||
var base_dir = -camera.global_transform.basis.z
|
||||
|
||||
for i in range(8):
|
||||
# Calculate spread
|
||||
var right = camera.global_transform.basis.x
|
||||
var up = camera.global_transform.basis.y
|
||||
|
||||
var x_spread = randf_range(-1.0, 1.0) * spread_angle
|
||||
var y_spread = randf_range(-1.0, 1.0) * spread_angle
|
||||
|
||||
var fire_dir = (base_dir + right * x_spread + up * y_spread).normalized()
|
||||
_spawn_custom_projectile(origin, fire_dir)
|
||||
|
||||
func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||
var proj = load("res://weapons/homing_projectile.gd").new()
|
||||
|
||||
# Mesh
|
||||
var mesh_instance = MeshInstance3D.new()
|
||||
var p_mesh = CylinderMesh.new()
|
||||
p_mesh.top_radius = 0.0
|
||||
p_mesh.bottom_radius = 0.02
|
||||
p_mesh.height = 0.1
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.8, 0.8, 0.1) # Yellow mini rocket
|
||||
p_mesh.material = mat
|
||||
mesh_instance.mesh = p_mesh
|
||||
mesh_instance.rotation.x = deg_to_rad(-90)
|
||||
proj.add_child(mesh_instance)
|
||||
|
||||
# Rotation
|
||||
var up_vec = Vector3.UP
|
||||
if abs(fire_dir.dot(up_vec)) > 0.99:
|
||||
up_vec = Vector3.RIGHT
|
||||
proj.transform.basis = Basis.looking_at(fire_dir, up_vec)
|
||||
|
||||
# Logic
|
||||
proj.weapon_source = self
|
||||
proj.turn_speed = 12.0 # Faster turn radius so they can aggressively home in
|
||||
|
||||
proj.position = origin # Start at origin to spread outward around camera
|
||||
proj.logical_source = 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 = 0.0
|
||||
proj.owner_player = player
|
||||
|
||||
# Explosive stats
|
||||
proj.explosion_radius = 3.0 # Tiny AoE effect
|
||||
proj.explosion_knockback = 5.0
|
||||
proj.can_self_damage = false
|
||||
|
||||
get_tree().current_scene.add_child(proj)
|
||||
Reference in New Issue
Block a user