Add industrial metal soundtrack with seamless loop

- assets/audio/music/zombie_industrial_loop.ogg: 57.60 s Ogg Vorbis loop
  generated locally with YuE2-3B on the Tesla V100 (score written for this
  track; vocals removed with HTDemucs). The loop is spliced at the track's
  structural repeat with a 0.6 s crossfade: measured seam dip <= 1.6 dB.
- scripts/main.gd: "Music" AudioStreamPlayer at -11 dB, started at boot on
  desktop and on the start click on Web (browser autoplay policy). It forces
  stream.loop at runtime because the Web staging step re-imports the OGG with
  loop=false.
- tools/test_music.gd: soundtrack test (loop, length, volume, playback,
  still playing after player death).
- docs/MUSIC.md: source, loop construction, verification, licence.
- docs/WEB-DEPLOY.md: release ab6054111f5919a8 notes.

Initial commit of the project.
This commit is contained in:
enne2
2026-10-10 23:11:13 +02:00
commit 956a15a525
199 changed files with 4563 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
extends RigidBody3D
signal detonated
const FUSE_SECONDS := 4.0
const BLAST_RADIUS := 5.0
const MAX_DAMAGE := 160.0
const Audio = preload("res://scripts/hl_audio.gd")
const Blood = preload("res://scripts/blood_effects.gd")
var age := 0.0
var exploded := false
var thrower: CharacterBody3D
func _ready() -> void:
name = "Grenade"
add_to_group("grenades")
collision_layer = 16
collision_mask = 1 | 4
mass = 0.4
continuous_cd = true
linear_damp = 0.15
angular_damp = 0.6
var physics := PhysicsMaterial.new()
physics.bounce = 0.45
physics.friction = 0.65
physics_material_override = physics
var collider := CollisionShape3D.new()
var sphere := SphereShape3D.new()
sphere.radius = 0.12
collider.shape = sphere
add_child(collider)
var model: Node3D = load("res://assets/weapons/grenade.scn").instantiate()
add_child(model)
for area in model.find_children("*", "Area3D", true, false):
area.collision_layer = 0
area.collision_mask = 0
for mesh in model.find_children("*", "MeshInstance3D", true, false):
mesh.layers = 2
if is_instance_valid(thrower):
add_collision_exception_with(thrower)
func _physics_process(delta: float) -> void:
age += delta
if age >= FUSE_SECONDS and not exploded:
exploded = true
call_deferred("_explode")
func _explode() -> void:
freeze = true
collision_layer = 0
collision_mask = 0
hide()
var world := get_parent() as Node3D
var point := global_position
var targets := get_tree().get_nodes_in_group("live_zombies")
if is_instance_valid(thrower) and not thrower.death_started:
targets.append(thrower)
for target in targets:
if not is_instance_valid(target):
continue
var center: Vector3 = target.global_position + Vector3.UP * 0.9
var distance := point.distance_to(center)
if distance > BLAST_RADIUS:
continue
var query := PhysicsRayQueryParameters3D.create(point, center, 1)
if not get_world_3d().direct_space_state.intersect_ray(query).is_empty():
continue
var damage := MAX_DAMAGE * (1.0 - distance / BLAST_RADIUS)
if target == thrower:
target.take_damage(damage)
else:
Blood.impact(world, center, (center - point).normalized())
target.take_damage(damage, center.y)
_effect(world, point)
detonated.emit()
queue_free()
func _effect(world: Node3D, point: Vector3) -> void:
var effect := Node3D.new()
effect.name = "GrenadeExplosion"
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)
var particles := CPUParticles3D.new()
particles.amount = 60
particles.lifetime = 0.85
particles.one_shot = true
particles.explosiveness = 1
particles.direction = Vector3.UP
particles.spread = 180
particles.initial_velocity_min = 2
particles.initial_velocity_max = 6
particles.gravity = Vector3(0, -3, 0)
var mesh := SphereMesh.new()
mesh.radius = 0.06
mesh.height = 0.12
var material := StandardMaterial3D.new()
material.albedo_color = Color(1, 0.3, 0.05)
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mesh.material = material
particles.mesh = mesh
var colors := Gradient.new()
colors.set_color(0, Color(1, 0.65, 0.1, 1))
colors.set_color(1, Color(0.15, 0.12, 0.1, 0))
particles.color_ramp = colors
effect.add_child(particles)
particles.emitting = true
world.get_tree().create_timer(4).timeout.connect(effect.queue_free)