diff --git a/assets/textures/sky/source_manifest.json b/assets/textures/sky/source_manifest.json new file mode 100644 index 0000000..a82db41 --- /dev/null +++ b/assets/textures/sky/source_manifest.json @@ -0,0 +1,8 @@ +{ + "spacert.tga": "1b5726f5c2f5e27ddfd8b583b6f31b77a7b85c8ae31f11a765e43dd41c1ba141", + "spacelf.tga": "18b1408a35244736380a2e98d39fbb02ff76bdb33262bd87f2de6b4c1895d4f3", + "spaceup.tga": "c3a548c1bf08859179a2922366c793c29ed322ace6ce5ae75f094fcc39457505", + "spacedn.tga": "1f746a42da3bc66c3fc5ab468ecf29a22587cd6413afad9bedf92518067c9b87", + "spaceft.tga": "73606c44eaa85615068b2a42c41f2cddef4ff49a27c872052ceff20543374ec2", + "spacebk.tga": "09dcf639be7050b87ee172b359c4cd499af66f5978bbb23cf61a3976e2fb4a24" +} diff --git a/assets/textures/sky/space_panorama.png b/assets/textures/sky/space_panorama.png new file mode 100644 index 0000000..01ffb87 Binary files /dev/null and b/assets/textures/sky/space_panorama.png differ diff --git a/assets/textures/sky/space_panorama.png.import b/assets/textures/sky/space_panorama.png.import new file mode 100644 index 0000000..7d247fd --- /dev/null +++ b/assets/textures/sky/space_panorama.png.import @@ -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 diff --git a/docs/HEALTH-SKY.md b/docs/HEALTH-SKY.md new file mode 100644 index 0000000..0a357bd --- /dev/null +++ b/docs/HEALTH-SKY.md @@ -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 diff --git a/docs/WEB-DEPLOY.md b/docs/WEB-DEPLOY.md index ee8f3b6..936e6c2 100644 --- a/docs/WEB-DEPLOY.md +++ b/docs/WEB-DEPLOY.md @@ -1,5 +1,17 @@ # 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/ Server: debian@enne2.net (bastion). Web root: /home/debian/public_html/zombie-fps/. diff --git a/scripts/main.gd b/scripts/main.gd index 87a8688..69a8bc4 100644 --- a/scripts/main.gd +++ b/scripts/main.gd @@ -31,8 +31,13 @@ func _ready() -> void: add_child(player) var envnode := WorldEnvironment.new() var env := Environment.new() - env.background_mode = Environment.BG_COLOR - env.background_color = Color(0.08, 0.12, 0.19) + env.background_mode = Environment.BG_SKY + 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_color = Color(0.65, 0.73, 0.85) env.ambient_light_energy = 0.65 diff --git a/scripts/player.gd b/scripts/player.gd index f9191d0..f2ec150 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -3,7 +3,19 @@ var camera: Camera3D var damage_pivot: Node3D var damage_feedback: CanvasLayer 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 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"): get_tree().reload_current_scene() # R o pulsante Ricarica a partita finita return + _regenerate_health(dt) _handle_actions() # 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. diff --git a/tools/prepare_sky.py b/tools/prepare_sky.py new file mode 100644 index 0000000..0edabe6 --- /dev/null +++ b/tools/prepare_sky.py @@ -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') diff --git a/tools/test_health_sky.gd b/tools/test_health_sky.gd new file mode 100644 index 0000000..33515fd --- /dev/null +++ b/tools/test_health_sky.gd @@ -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()