Add one HP per second regeneration and Half-Life starfield sky

This commit is contained in:
enne2
2026-10-11 00:29:28 +02:00
parent f246ede94f
commit d74af7b550
9 changed files with 199 additions and 3 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"spacert.tga": "1b5726f5c2f5e27ddfd8b583b6f31b77a7b85c8ae31f11a765e43dd41c1ba141",
"spacelf.tga": "18b1408a35244736380a2e98d39fbb02ff76bdb33262bd87f2de6b4c1895d4f3",
"spaceup.tga": "c3a548c1bf08859179a2922366c793c29ed322ace6ce5ae75f094fcc39457505",
"spacedn.tga": "1f746a42da3bc66c3fc5ab468ecf29a22587cd6413afad9bedf92518067c9b87",
"spaceft.tga": "73606c44eaa85615068b2a42c41f2cddef4ff49a27c872052ceff20543374ec2",
"spacebk.tga": "09dcf639be7050b87ee172b359c4cd499af66f5978bbb23cf61a3976e2fb4a24"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bf7v2lohgsvfa"
path="res://.godot/imported/space_panorama.png-9d641b39004f6127eea8a85f76e8ffe9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/textures/sky/space_panorama.png"
dest_files=["res://.godot/imported/space_panorama.png-9d641b39004f6127eea8a85f76e8ffe9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
+34
View File
@@ -0,0 +1,34 @@
# Health regeneration and starry sky
- Living players recover exactly one HP per elapsed gameplay second, including combat.
Maximum is 100 HP; no healing or resurrection after death. Physics time accumulator
handles frame-rate variation; time at full HP does not bank healing.
- `scripts/main.gd` uses a `PanoramaSkyMaterial` as the environment background.
Ambient light remains the existing constant color; sky reflections are disabled.
- Compared local Half-Life skies `night`, `space`, and several `neb*` sets. `space`
was selected for a plain star field (`night` is cloudy; nebulae are colorful).
- `assets/textures/sky/space_panorama.png`: 2048x1024 equirectangular conversion of
the six Valve `space*.tga` faces, not a newly generated texture. The original faces
are low resolution, so stars retain their original soft appearance. Source hashes
are in `source_manifest.json`; original Steam files are never modified.
Reproduce (Pillow and FFmpeg with v360 required):
```sh
python3 tools/prepare_sky.py /path/to/Half-Life/valve/gfx/env
```
Verify with installed Godot 4.7.1:
```sh
"$G" --headless --path . --script res://tools/test_health_sky.gd
"$G" --display-driver x11 --rendering-method gl_compatibility --path . \
--script res://tools/test_health_sky.gd -- --screenshot
```
Screenshot: `/tmp/zombie-star-window.png`. Test checks discrete timing, maximum,
no healing after death, panorama dimensions, and unchanged ambient light. Existing
music, grenade, wave, death, damage, and touch tests also pass. The existing resource
leak/in-use warnings at test shutdown remain; they are not fixed by this change.
Sky format reference: https://docs.godotengine.org/en/stable/classes/class_panoramaskymaterial.html
+12
View File
@@ -1,5 +1,17 @@
# Web build and compressed deployment # Web build and compressed deployment
## Health + starry sky release: 31564f79b9f5e299
See `docs/HEALTH-SKY.md`: +1 HP/second while alive, capped at 100; Half-Life
`space` panorama visible outside windows. PCK 3,938,672 bytes; gzip payload total
12,395,646 bytes. Previous releases retained; root backup `index-before-health-sky.html`.
Build encountered an import-thread crash (`propagate_notification` off caller thread,
signal 11). Workaround applied only to generated staging `project.godot`:
`[editor] import/use_multiple_threads=false`; serial re-import and export succeeded.
Upstream similar issue: https://github.com/godotengine/godot/issues/96242
URL: https://enne2.net/pub/zombie-fps/ URL: https://enne2.net/pub/zombie-fps/
Server: debian@enne2.net (bastion). Server: debian@enne2.net (bastion).
Web root: /home/debian/public_html/zombie-fps/. Web root: /home/debian/public_html/zombie-fps/.
+7 -2
View File
@@ -31,8 +31,13 @@ func _ready() -> void:
add_child(player) add_child(player)
var envnode := WorldEnvironment.new() var envnode := WorldEnvironment.new()
var env := Environment.new() var env := Environment.new()
env.background_mode = Environment.BG_COLOR env.background_mode = Environment.BG_SKY
env.background_color = Color(0.08, 0.12, 0.19) var sky_material := PanoramaSkyMaterial.new()
sky_material.panorama = load("res://assets/textures/sky/space_panorama.png")
env.sky = Sky.new()
env.sky.sky_material = sky_material
# Solo sfondo: illuminazione e riflessi non dipendono dal cielo scuro.
env.reflected_light_source = Environment.REFLECTION_SOURCE_DISABLED
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.65, 0.73, 0.85) env.ambient_light_color = Color(0.65, 0.73, 0.85)
env.ambient_light_energy = 0.65 env.ambient_light_energy = 0.65
+14 -1
View File
@@ -3,7 +3,19 @@ var camera: Camera3D
var damage_pivot: Node3D var damage_pivot: Node3D
var damage_feedback: CanvasLayer var damage_feedback: CanvasLayer
const DamageFeedback = preload("res://scripts/damage_feedback.gd") const DamageFeedback = preload("res://scripts/damage_feedback.gd")
var hp := 100.0 const MAX_HP := 100.0
var hp := MAX_HP
var _regen_elapsed := 0.0
## Discreto: +1 HP per secondo di gioco, mai oltre il massimo o dopo morte.
func _regenerate_health(dt: float) -> void:
if hp <= 0 or death_started or hp >= MAX_HP:
_regen_elapsed = 0.0
return
_regen_elapsed += dt
while _regen_elapsed >= 1.0:
_regen_elapsed -= 1.0
hp = minf(MAX_HP, hp + 1.0)
var ammo := 30 var ammo := 30
var cooldown := 0.0 var cooldown := 0.0
var grenade_cooldown := 0.0 var grenade_cooldown := 0.0
@@ -127,6 +139,7 @@ func _physics_process(dt: float) -> void:
if Input.is_action_just_pressed("reload"): if Input.is_action_just_pressed("reload"):
get_tree().reload_current_scene() # R o pulsante Ricarica a partita finita get_tree().reload_current_scene() # R o pulsante Ricarica a partita finita
return return
_regenerate_health(dt)
_handle_actions() _handle_actions()
# Deadzone esplicita piccola (0.2): con la 0.5 delle azioni la corsa utile del # Deadzone esplicita piccola (0.2): con la 0.5 delle azioni la corsa utile del
# joystick touch si dimezzerebbe, mentre per tastiera/joypad nulla cambia. # joystick touch si dimezzerebbe, mentre per tastiera/joypad nulla cambia.
+30
View File
@@ -0,0 +1,30 @@
"""Convert local Half-Life space sky faces to a Godot panorama (Pillow + ffmpeg)."""
import argparse
import hashlib
import json
import pathlib
import subprocess
import tempfile
from PIL import Image
parser = argparse.ArgumentParser()
parser.add_argument('source', type=pathlib.Path, help='Half-Life valve/gfx/env directory')
args = parser.parse_args()
out = pathlib.Path(__file__).resolve().parents[1] / 'assets/textures/sky'
out.mkdir(parents=True, exist_ok=True)
faces = ['rt', 'lf', 'up', 'dn', 'ft', 'bk']
images = [Image.open(args.source / f'space{s}.tga').convert('RGB') for s in faces]
w, h = images[0].size
assert w == h and all(im.size == (w, h) for im in images)
strip = Image.new('RGB', (6*w, h))
for i, im in enumerate(images):
strip.paste(im, (i*w, 0))
with tempfile.TemporaryDirectory() as tmp:
src = pathlib.Path(tmp) / 'cube.png'
strip.save(src)
subprocess.run(['ffmpeg', '-y', '-loglevel', 'error', '-i', str(src),
'-vf', 'v360=input=c6x1:output=e:in_forder=rludfb:w=2048:h=1024',
'-frames:v', '1', str(out / 'space_panorama.png')], check=True)
manifest = {f'space{s}.tga': hashlib.sha256((args.source / f'space{s}.tga').read_bytes()).hexdigest() for s in faces}
(out / 'source_manifest.json').write_text(json.dumps(manifest, indent=2) + '\n')
print(out / 'space_panorama.png')
+54
View File
@@ -0,0 +1,54 @@
extends SceneTree
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
var world = load("res://scenes/main.tscn").instantiate()
root.add_child(world)
world.set_process(false)
var player = world.player
player.set_physics_process(false)
player.hp = 90.0
player._regenerate_health(0.5)
assert(player.hp == 90.0)
player._regenerate_health(0.5)
assert(player.hp == 91.0)
player._regenerate_health(3.0)
assert(player.hp == 94.0)
print("PASS: +1 HP ogni secondo, recupero frame lunghi")
player.hp = 99.5
player._regenerate_health(5.0)
assert(player.hp == 100.0)
player._regenerate_health(0.0)
player.hp = 90.0
player._regenerate_health(0.5)
assert(player.hp == 90.0)
print("PASS: limite 100 HP, nessun accumulo a vita piena")
if "--screenshot" in OS.get_cmdline_user_args():
player.position = Vector3(0, 0, -6)
player.camera.rotation = Vector3.ZERO
await create_timer(0.5).timeout
await RenderingServer.frame_post_draw
root.get_texture().get_image().save_png("/tmp/zombie-star-window.png")
player.hp = 0
player._regenerate_health(10.0)
assert(player.hp == 0)
player.hp = 30
player.death_started = true
player._regenerate_health(10.0)
assert(player.hp == 30)
print("PASS: nessuna rigenerazione dopo morte")
var environment: Environment
for node in world.get_children():
if node is WorldEnvironment:
environment = node.environment
assert(environment.background_mode == Environment.BG_SKY)
assert(environment.sky.sky_material is PanoramaSkyMaterial)
var texture = environment.sky.sky_material.panorama
assert(texture.get_width() == 2048 and texture.get_height() == 1024)
assert(environment.ambient_light_source == Environment.AMBIENT_SOURCE_COLOR)
print("PASS: panorama stellato caricato, illuminazione stanza preservata")
world.queue_free()
await process_frame
quit()