Detonate rifle grenades on impact with original Half-Life fireball

This commit is contained in:
enne2
2026-10-15 14:20:07 +02:00
parent ae32ad9615
commit 5761e160c1
12 changed files with 581 additions and 81 deletions
+46 -19
View File
@@ -1,6 +1,8 @@
extends RigidBody3D
signal detonated
const FUSE_SECONDS := 4.0
# No timed fuse: the rifle grenade detonates on its first physical contact.
const MAX_LIFETIME := 15.0
const Explosion = preload("res://scripts/grenade_explosion.gd")
const BLAST_RADIUS := 5.0
const MAX_DAMAGE := 160.0
const Audio = preload("res://scripts/hl_audio.gd")
@@ -8,18 +10,24 @@ const Blood = preload("res://scripts/blood_effects.gd")
var age := 0.0
var exploded := false
var thrower: CharacterBody3D
var previous_position := Vector3.ZERO
var impact_position := Vector3.ZERO
func _ready() -> void:
name = "Grenade"
add_to_group("grenades")
previous_position = global_position
collision_layer = 16
collision_mask = 1 | 4
mass = 0.4
continuous_cd = true
contact_monitor = true
max_contacts_reported = 8
body_entered.connect(_on_body_entered)
linear_damp = 0.15
angular_damp = 0.6
var physics := PhysicsMaterial.new()
physics.bounce = 0.45
physics.bounce = 0.0
physics.friction = 0.65
physics_material_override = physics
var collider := CollisionShape3D.new()
@@ -39,9 +47,36 @@ func _ready() -> void:
func _physics_process(delta: float) -> void:
age += delta
if age >= FUSE_SECONDS and not exploded:
exploded = true
call_deferred("_explode")
# Window panes are Area3D on layer 8, not PhysicsBody3D. A swept ray catches
# those too, even if a fast grenade crosses the whole pane in one tick.
if not exploded and not previous_position.is_equal_approx(global_position):
var query := PhysicsRayQueryParameters3D.create(previous_position, global_position, 8)
query.collide_with_areas = true
query.collide_with_bodies = false
var hit := get_world_3d().direct_space_state.intersect_ray(query)
if not hit.is_empty():
_request_detonation(hit.position + hit.normal * 0.13)
previous_position = global_position
# Safety cleanup only: a projectile lost outside the map must not explode in air.
if age >= MAX_LIFETIME and not exploded:
queue_free()
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
if not exploded and state.get_contact_count() > 0:
# Save the physics position before deferring scene/shape changes. The sphere
# centre stays on the free side of the surface, also for damage LOS rays.
_request_detonation(state.transform.origin)
func _on_body_entered(body: Node) -> void:
if body != thrower:
_request_detonation(global_position)
func _request_detonation(point: Vector3) -> void:
if exploded:
return
exploded = true
impact_position = point
call_deferred("_explode")
func _explode() -> void:
freeze = true
@@ -49,7 +84,7 @@ func _explode() -> void:
collision_mask = 0
hide()
var world := get_parent() as Node3D
var point := global_position
var point := impact_position
var targets := get_tree().get_nodes_in_group("live_zombies")
if is_instance_valid(thrower) and not thrower.death_started:
targets.append(thrower)
@@ -74,20 +109,13 @@ func _explode() -> void:
queue_free()
func _effect(world: Node3D, point: Vector3) -> void:
var effect := Node3D.new()
effect.name = "GrenadeExplosion"
var effect := Explosion.new()
world.add_child(effect)
effect.global_position = point
Audio.play(effect, "weapons/explode%d.wav" % randi_range(3, 5), true, -7)
var light := OmniLight3D.new()
light.light_color = Color(1, 0.45, 0.08)
light.light_energy = 8
light.omni_range = 10
effect.add_child(light)
var tween := effect.create_tween()
tween.tween_property(light, "light_energy", 0.0, 0.3)
# Secondary embers only: the original fireball is now the main visual.
var particles := CPUParticles3D.new()
particles.amount = 60
particles.amount = 18
particles.lifetime = 0.85
particles.one_shot = true
particles.explosiveness = 1
@@ -97,8 +125,8 @@ func _effect(world: Node3D, point: Vector3) -> void:
particles.initial_velocity_max = 6
particles.gravity = Vector3(0, -3, 0)
var mesh := SphereMesh.new()
mesh.radius = 0.06
mesh.height = 0.12
mesh.radius = 0.018
mesh.height = 0.036
var material := StandardMaterial3D.new()
material.albedo_color = Color(1, 0.3, 0.05)
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
@@ -110,4 +138,3 @@ func _effect(world: Node3D, point: Vector3) -> void:
particles.color_ramp = colors
effect.add_child(particles)
particles.emitting = true
world.get_tree().create_timer(4).timeout.connect(effect.queue_free)