From 5761e160c17fb637c96e25405e0ad745c77da8e0 Mon Sep 17 00:00:00 2001 From: enne2 Date: Thu, 15 Oct 2026 14:20:07 +0200 Subject: [PATCH] Detonate rifle grenades on impact with original Half-Life fireball --- assets/effects/explosion/source_manifest.json | 79 +++++++ .../effects/explosion/zerogxplode_atlas.png | Bin 0 -> 41685 bytes .../explosion/zerogxplode_atlas.png.import | 40 ++++ docs/GRENADES-WAVES.md | 92 ++++++-- docs/WEB-DEPLOY.md | 22 ++ scripts/grenade.gd | 65 ++++-- scripts/grenade_explosion.gd | 61 ++++++ scripts/grenade_explosion.gd.uid | 1 + tools/extract_explosion_sprite.py | 66 ++++++ tools/shot_grenade.gd | 34 +++ tools/shot_grenade.gd.uid | 1 + tools/test_grenade.gd | 201 +++++++++++++----- 12 files changed, 581 insertions(+), 81 deletions(-) create mode 100644 assets/effects/explosion/source_manifest.json create mode 100644 assets/effects/explosion/zerogxplode_atlas.png create mode 100644 assets/effects/explosion/zerogxplode_atlas.png.import create mode 100644 scripts/grenade_explosion.gd create mode 100644 scripts/grenade_explosion.gd.uid create mode 100644 tools/extract_explosion_sprite.py create mode 100644 tools/shot_grenade.gd create mode 100644 tools/shot_grenade.gd.uid diff --git a/assets/effects/explosion/source_manifest.json b/assets/effects/explosion/source_manifest.json new file mode 100644 index 0000000..aaac002 --- /dev/null +++ b/assets/effects/explosion/source_manifest.json @@ -0,0 +1,79 @@ +{ + "source": "Half-Life/valve/sprites/zerogxplode.spr", + "source_sha256": "c0fe1514c05b1d05691afcf3e76d7c928c8f7720125260eaa6ac276cfdf986ce", + "destination": "assets/effects/explosion/zerogxplode_atlas.png", + "destination_sha256": "cb4268e353c0aaee86a125a999b4ab1d219355849f736b5c529d3d9d3ce7eba6", + "format": "IDSP v2 / SPR_ADDITIVE", + "frame_size": [ + 64, + 64 + ], + "frame_count": 15, + "columns": 5, + "rows": 3, + "gutter": 2, + "origins": [ + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ], + [ + -32, + 32 + ] + ], + "fps": 15, + "fps_source": "https://github.com/ValveSoftware/halflife/blob/master/dlls/ggrenade.cpp" +} diff --git a/assets/effects/explosion/zerogxplode_atlas.png b/assets/effects/explosion/zerogxplode_atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..3c509badf4910a795c4fc1e5ff547b2760a41327 GIT binary patch literal 41685 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 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) diff --git a/scripts/grenade_explosion.gd b/scripts/grenade_explosion.gd new file mode 100644 index 0000000..5b859c6 --- /dev/null +++ b/scripts/grenade_explosion.gd @@ -0,0 +1,61 @@ +extends Node3D +## Original Half-Life fireball, 15 frames at 15 FPS, additive camera-facing quad. +## A PNG atlas avoids the native GoldSrc importer in Web exports. +const ATLAS = preload("res://assets/effects/explosion/zerogxplode_atlas.png") +const FRAME_COUNT := 15 +const FPS := 15.0 +const DURATION := FRAME_COUNT / FPS +const SIZE := 3.2 +var elapsed := 0.0 +var frame_index := 0 +var fireball: MeshInstance3D +var material: StandardMaterial3D + +func _ready() -> void: + name = "GrenadeExplosion" + material = StandardMaterial3D.new() + material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + material.blend_mode = BaseMaterial3D.BLEND_MODE_ADD + material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED + material.cull_mode = BaseMaterial3D.CULL_DISABLED + material.albedo_texture = ATLAS + material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR + material.texture_repeat = false + # Two-pixel black gutters prevent adjacent frames bleeding under filtering. + material.uv1_scale = Vector3(64.0 / 340.0, 64.0 / 204.0, 1) + fireball = MeshInstance3D.new() + fireball.name = "Fireball" + fireball.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + var quad := QuadMesh.new() + quad.size = Vector2(SIZE, SIZE) + quad.material = material + fireball.mesh = quad + # Raise the fireball, but leave the damage origin at the physical impact. + fireball.position.y = 0.8 + add_child(fireball) + _set_frame(0) + var light := OmniLight3D.new() + light.name = "Flash" + light.light_color = Color(1, 0.45, 0.08) + light.light_energy = 8 + light.omni_range = 10 + light.shadow_enabled = false + add_child(light) + create_tween().tween_property(light, "light_energy", 0.0, 0.3) + # Keep the node alive for the original explosion sound to finish. + get_tree().create_timer(4).timeout.connect(queue_free) + +func _process(delta: float) -> void: + elapsed += delta + if elapsed >= DURATION: + fireball.hide() + set_process(false) + return + _set_frame(mini(int(elapsed * FPS), FRAME_COUNT - 1)) + +func _set_frame(index: int) -> void: + frame_index = index + material.uv1_offset = Vector3( + (float(index % 5) * 68 + 2) / 340.0, + (float(index / 5) * 68 + 2) / 204.0, 0) diff --git a/scripts/grenade_explosion.gd.uid b/scripts/grenade_explosion.gd.uid new file mode 100644 index 0000000..73f3fbf --- /dev/null +++ b/scripts/grenade_explosion.gd.uid @@ -0,0 +1 @@ +uid://d030pl0ug1qcs diff --git a/tools/extract_explosion_sprite.py b/tools/extract_explosion_sprite.py new file mode 100644 index 0000000..2ec3e31 --- /dev/null +++ b/tools/extract_explosion_sprite.py @@ -0,0 +1,66 @@ +"""Convert the original GoldSrc additive explosion into a portable PNG atlas. + +Read-only on the Half-Life installation. No native SPR importer needed at runtime. +""" +import argparse +import hashlib +import json +import struct +from pathlib import Path + +from PIL import Image + +ROOT = Path(__file__).resolve().parents[1] + + +def extract(source: Path, destination: Path) -> dict: + raw = source.read_bytes() + ident, version, orientation, fmt, radius, width, height, count, beam, sync = ( + struct.unpack_from('<4siiifiiifi', raw) + ) + assert (ident, version, fmt) == (b'IDSP', 2, 1), 'Expected GoldSrc additive SPR v2' + palette_count = struct.unpack_from(' void: + call_deferred("run_test") + +func run_test() -> void: + root.size = Vector2i(1280, 720) + var world = load("res://scenes/main.tscn").instantiate() + root.add_child(world) + world.spawning = true + world.start_screen.hide() + world.player.set_physics_process(false) + world.player.position = Vector3(0, 0.1, 4) + world.player.camera.look_at(Vector3(0, 1, -1)) + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + await create_timer(0.3).timeout + var grenade = load("res://scripts/grenade.gd").new() + grenade.position = Vector3(0, 0.6, -1) + world.add_child(grenade) + grenade.linear_velocity = Vector3(0, -4, 0) + await grenade.detonated + await process_frame + var effect = world.get_node("GrenadeExplosion") + effect.set_process(false) + for index in [0, 3, 7, 11, 14]: + effect.fireball.show() + effect._set_frame(index) + await process_frame + await RenderingServer.frame_post_draw + root.get_texture().get_image().save_png("/tmp/zombie-grenade-frame-%02d.png" % index) + print("SHOT: original sprite frames 0/3/7/11/14 at real floor impact") + world.queue_free() + await process_frame + quit() diff --git a/tools/shot_grenade.gd.uid b/tools/shot_grenade.gd.uid new file mode 100644 index 0000000..17ca3cf --- /dev/null +++ b/tools/shot_grenade.gd.uid @@ -0,0 +1 @@ +uid://b14ugdkeste0s diff --git a/tools/test_grenade.gd b/tools/test_grenade.gd index ecb2cbb..7b4518e 100644 --- a/tools/test_grenade.gd +++ b/tools/test_grenade.gd @@ -1,70 +1,175 @@ extends SceneTree -var detonation_time := -1 +## Real physics contacts + blast regression + original sprite playback, no public API. +const Grenade = preload("res://scripts/grenade.gd") +const Explosion = preload("res://scripts/grenade_explosion.gd") +var checks := 0 +var failures := 0 +var detonations := 0 +var impact_time := 0 + func _initialize() -> void: call_deferred("run_test") -func run_test() -> void: + +func check(ok: bool, label: String) -> void: + checks += 1 + if not ok: + failures += 1 + print("PASS: " if ok else "FAIL: ", label) + +func body(world: Node3D, at: Vector3, size: Vector3, layer: int = 1) -> StaticBody3D: + var result := StaticBody3D.new() + result.position = at + result.collision_layer = layer + result.collision_mask = 16 + var shape := CollisionShape3D.new() + var box := BoxShape3D.new() + box.size = size + shape.shape = box + result.add_child(shape) + world.add_child(result) + return result + +func projectile(world: Node3D, at: Vector3, speed: Vector3) -> RigidBody3D: + var grenade := Grenade.new() + grenade.position = at + world.add_child(grenade) + grenade.linear_velocity = speed + grenade.detonated.connect(func(): + detonations += 1 + impact_time = Time.get_ticks_msec()) + return grenade + +func contact_case(label: String, at: Vector3, size: Vector3, layer: int, + start: Vector3, speed: Vector3) -> void: var world := Node3D.new() root.add_child(world) - var room := Node3D.new() - room.set_script(load("res://scripts/room.gd")) - world.add_child(room) - var player := CharacterBody3D.new() - player.set_script(load("res://scripts/player.gd")) + var obstacle: Node3D + if layer == 8: + var area := Area3D.new() + area.position = at + area.collision_layer = 8 + area.collision_mask = 0 + var shape := CollisionShape3D.new() + var box := BoxShape3D.new() + box.size = size + shape.shape = box + area.add_child(shape) + world.add_child(area) + obstacle = area + else: + obstacle = body(world, at, size, layer) + if layer == 4: + obstacle.set_script(load("res://tools/grenade_test_target.gd")) + obstacle.add_to_group("live_zombies") + await physics_frame + detonations = 0 + var started := Time.get_ticks_msec() + var grenade := projectile(world, start, speed) + for i in range(120): + await physics_frame + if not is_instance_valid(grenade): + break + check(not is_instance_valid(grenade) and detonations == 1, + label + ": esplode una sola volta al contatto") + check(impact_time - started < 1500 and detonations == 1, + label + ": nessuna attesa della vecchia miccia") + if layer == 4: + check(obstacle.get("damage_taken") > 0, "nemico colpito: riceve il danno esplosivo") + world.queue_free() + await process_frame + +func run_test() -> void: + await contact_case("pavimento", Vector3(0, -0.1, 0), Vector3(20, 0.2, 20), 1, + Vector3(0, 1.2, 0), Vector3(0, -5, 0)) + await contact_case("scrivania", Vector3(0, 0.7, -2), Vector3(2, 1.4, 1), 1, + Vector3(0, 1.2, 0), Vector3(0, 0, -12)) + await contact_case("parete sottile / CCD", Vector3(0, 1.2, -2), Vector3(4, 4, 0.05), 1, + Vector3(0, 1.2, 0), Vector3(0, 0, -80)) + await contact_case("nemico", Vector3(0, 0.9, -2), Vector3(0.7, 1.8, 0.7), 4, + Vector3(0, 1.2, 0), Vector3(0, 0, -12)) + await contact_case("vetro Area3D / attraversamento rapido", Vector3(0, 1.2, -2), + Vector3(4, 4, 0.03), 8, Vector3(0, 1.2, 0), Vector3(0, 0, -80)) + + var world := Node3D.new() + root.add_child(world) + body(world, Vector3(0, -0.1, 0), Vector3(30, 0.2, 30)) + var player = load("res://scripts/player.gd").new() player.position = Vector3(0, 0.1, 4) world.add_child(player) player.hp = 10000 Input.mouse_mode = Input.MOUSE_MODE_VISIBLE await physics_frame var grenade: RigidBody3D = player.throw_grenade() - assert(grenade != null) - assert(player.throw_grenade() == null) - assert(is_equal_approx(player.grenade_cooldown, 15)) - assert(grenade.physics_material_override.bounce > 0) - grenade.detonated.connect(func(): detonation_time = Time.get_ticks_msec()) var started := Time.get_ticks_msec() - var start := grenade.position - await create_timer(0.5).timeout - assert(grenade.position.distance_to(start) > 1) - assert(grenade.linear_velocity.y < 3) + check(grenade != null and player.throw_grenade() == null + and is_equal_approx(player.grenade_cooldown, 15), "lancio e cooldown 15s invariati") + check(grenade.contact_monitor and grenade.max_contacts_reported > 0 + and grenade.continuous_cd and grenade.physics_material_override.bounce == 0, + "contatti abilitati, CCD e nessun rimbalzo") + var start := grenade.global_position + await create_timer(0.15).timeout + check(is_instance_valid(grenade) and grenade.position.distance_to(start) > 1 + and grenade.linear_velocity.y < 3, "ignora il tiratore; moto e gravità invariati") + # Dispose flight before isolating blast LOS/range (no reliance on random bounces). + if is_instance_valid(grenade): + grenade.queue_free() + await process_frame var targets: Array[Node3D] = [] for pos in [Vector3(0, 0, -2), Vector3(2, 0, 0), Vector3(0, 0, -7)]: - var target := Node3D.new() - target.set_script(load("res://tools/grenade_test_target.gd")) + var target = load("res://tools/grenade_test_target.gd").new() target.position = pos world.add_child(target) targets.append(target) - var wall := StaticBody3D.new() - wall.position = Vector3(1, 1.5, 0) - var shape := CollisionShape3D.new() - var box := BoxShape3D.new() - box.size = Vector3(0.2, 3, 2) - shape.shape = box - wall.add_child(shape) - world.add_child(wall) - # Isolate explosion coverage from the projectile's random rotation/bounces. + body(world, Vector3(1, 1.5, 0), Vector3(0.2, 3, 2)) + await physics_frame + grenade = projectile(world, Vector3(0, 0.3, 0), Vector3.ZERO) grenade.freeze = true - grenade.global_position = Vector3(0, 0.3, 0) - await create_timer(3.15).timeout - assert(is_instance_valid(grenade) and not grenade.exploded) - await create_timer(0.6).timeout - assert(detonation_time >= 0) - var seconds := (detonation_time - started) / 1000.0 - assert(seconds >= 3.8 and seconds <= 4.3) - assert(targets[0].damage_taken > 0) - assert(targets[1].damage_taken == 0) # behind a wall - assert(targets[2].damage_taken == 0) # beyond blast radius - assert(player.hp < 10000) - assert(player.throw_grenade() == null) - # Wait until the real cooldown expires, without changing the game timer. - await create_timer(player.grenade_cooldown + 0.1).timeout - assert(player.grenade_cooldown == 0) - assert(player.throw_grenade() != null) + grenade.thrower = player + detonations = 0 + grenade._request_detonation(grenade.global_position) + grenade._request_detonation(grenade.global_position) + await process_frame + await process_frame + check(detonations == 1, "richieste simultanee non duplicano esplosione e danni") + check(targets[0].damage_taken > 0 and targets[1].damage_taken == 0 + and targets[2].damage_taken == 0, "danno, occlusione delle pareti e raggio 5m invariati") + check(player.hp < 10000, "il tiratore vicino subisce ancora danni") + # No fuse: an airborne grenade is not allowed to explode after four seconds. + grenade = projectile(world, Vector3(0, 8, 0), Vector3.ZERO) + grenade.freeze = true + detonations = 0 + await create_timer(4.2).timeout + check(is_instance_valid(grenade) and not grenade.exploded and detonations == 0, + "nessuna detonazione in aria dopo 4s") + grenade.age = Grenade.MAX_LIFETIME + await physics_frame + await process_frame + check(not is_instance_valid(grenade) and detonations == 0, + "proiettile perso: cleanup senza esplosione") + + var effect := Explosion.new() + world.add_child(effect) + effect.set_process(false) + check(effect.ATLAS.get_size() == Vector2(340, 204) + and effect.material.blend_mode == BaseMaterial3D.BLEND_MODE_ADD + and effect.material.billboard_mode == BaseMaterial3D.BILLBOARD_ENABLED, + "sprite originale: atlas con gutter, blending additivo, billboard") + effect._process(14.1 / Explosion.FPS) + check(effect.frame_index == 14 and effect.fireball.visible + and is_equal_approx(effect.material.uv1_offset.y, 138.0 / 204.0), + "15 fotogrammi a 15 FPS: ultimo frame e UV corretti") + effect._process(0.1) + check(not effect.fireball.visible, "animazione one-shot: sparisce dopo un secondo") + # Wait for the real countdown; never reset or accelerate the player's timer. + await create_timer(player.grenade_cooldown + 0.15).timeout + check(Time.get_ticks_msec() - started >= 15000 + and player.grenade_cooldown == 0 and player.throw_grenade() != null, + "nuovo lancio disponibile dopo il cooldown") var main = load("res://scripts/main.gd").new() - assert(main.zombies_for_wave(1) == 6) - assert(main.zombies_for_wave(2) == 8) - assert(main.zombies_for_wave(11) == 26) + check(main.zombies_for_wave(1) == 6 and main.zombies_for_wave(11) == 26, + "regressione crescita ondate oltre 24 zombie") main.free() - print("PASS: thrown physics/gravity, 4s fuse (", seconds, "), 15s cooldown, blast LOS/range/self-damage, wave growth beyond 24") world.queue_free() await process_frame - quit() + print(checks, " controlli superati: ", checks - failures, "; falliti: ", failures) + quit(1 if failures else 0)