From 1b324cf38434cb89009f8a09fe222e8ccb4b49e7 Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:22 +1000 Subject: [PATCH 1/9] [M1.5][Codex] Configure Art V2 imports --- .../gate_warden_attack_1.png.import | 40 +++++++++++++++++++ .../gate_warden_attack_2.png.import | 40 +++++++++++++++++++ .../gate_warden/gate_warden_death.png.import | 40 +++++++++++++++++++ .../gate_warden/gate_warden_hurt.png.import | 40 +++++++++++++++++++ .../gate_warden/gate_warden_idle.png.import | 40 +++++++++++++++++++ .../gate_warden/gate_warden_walk.png.import | 40 +++++++++++++++++++ .../ghost_archer/ghost_archer_aim.png.import | 40 +++++++++++++++++++ .../ghost_archer_death.png.import | 40 +++++++++++++++++++ .../ghost_archer/ghost_archer_hurt.png.import | 40 +++++++++++++++++++ .../ghost_archer/ghost_archer_idle.png.import | 40 +++++++++++++++++++ .../ghost_archer_retreat.png.import | 40 +++++++++++++++++++ .../ghost_archer_shoot.png.import | 40 +++++++++++++++++++ .../melee_ghost/melee_ghost_attack.png.import | 40 +++++++++++++++++++ .../melee_ghost/melee_ghost_death.png.import | 40 +++++++++++++++++++ .../melee_ghost/melee_ghost_hurt.png.import | 40 +++++++++++++++++++ .../melee_ghost/melee_ghost_idle.png.import | 40 +++++++++++++++++++ .../melee_ghost/melee_ghost_walk.png.import | 40 +++++++++++++++++++ .../characters/player/player_death.png.import | 40 +++++++++++++++++++ .../characters/player/player_fall.png.import | 40 +++++++++++++++++++ .../player/player_heavy_attack.png.import | 40 +++++++++++++++++++ .../characters/player/player_hurt.png.import | 40 +++++++++++++++++++ .../characters/player/player_idle.png.import | 40 +++++++++++++++++++ .../characters/player/player_jump.png.import | 40 +++++++++++++++++++ .../player/player_light_attack_1.png.import | 40 +++++++++++++++++++ .../player/player_light_attack_2.png.import | 40 +++++++++++++++++++ .../characters/player/player_run.png.import | 40 +++++++++++++++++++ assets_v2/effects/blade_slash.png.import | 40 +++++++++++++++++++ assets_v2/effects/death_dissolve.png.import | 40 +++++++++++++++++++ assets_v2/effects/hit.png.import | 40 +++++++++++++++++++ assets_v2/icons/icon_ghostfire.png.import | 40 +++++++++++++++++++ assets_v2/icons/icon_obsession.png.import | 40 +++++++++++++++++++ assets_v2/icons/icon_qi.png.import | 40 +++++++++++++++++++ assets_v2/icons/icon_songdao.png.import | 40 +++++++++++++++++++ assets_v2/icons/icon_talisman.png.import | 40 +++++++++++++++++++ assets_v2/icons/icon_yang.png.import | 40 +++++++++++++++++++ .../projectiles/ghost_fire_arrow.png.import | 40 +++++++++++++++++++ 36 files changed, 1440 insertions(+) create mode 100644 assets_v2/characters/gate_warden/gate_warden_attack_1.png.import create mode 100644 assets_v2/characters/gate_warden/gate_warden_attack_2.png.import create mode 100644 assets_v2/characters/gate_warden/gate_warden_death.png.import create mode 100644 assets_v2/characters/gate_warden/gate_warden_hurt.png.import create mode 100644 assets_v2/characters/gate_warden/gate_warden_idle.png.import create mode 100644 assets_v2/characters/gate_warden/gate_warden_walk.png.import create mode 100644 assets_v2/characters/ghost_archer/ghost_archer_aim.png.import create mode 100644 assets_v2/characters/ghost_archer/ghost_archer_death.png.import create mode 100644 assets_v2/characters/ghost_archer/ghost_archer_hurt.png.import create mode 100644 assets_v2/characters/ghost_archer/ghost_archer_idle.png.import create mode 100644 assets_v2/characters/ghost_archer/ghost_archer_retreat.png.import create mode 100644 assets_v2/characters/ghost_archer/ghost_archer_shoot.png.import create mode 100644 assets_v2/characters/melee_ghost/melee_ghost_attack.png.import create mode 100644 assets_v2/characters/melee_ghost/melee_ghost_death.png.import create mode 100644 assets_v2/characters/melee_ghost/melee_ghost_hurt.png.import create mode 100644 assets_v2/characters/melee_ghost/melee_ghost_idle.png.import create mode 100644 assets_v2/characters/melee_ghost/melee_ghost_walk.png.import create mode 100644 assets_v2/characters/player/player_death.png.import create mode 100644 assets_v2/characters/player/player_fall.png.import create mode 100644 assets_v2/characters/player/player_heavy_attack.png.import create mode 100644 assets_v2/characters/player/player_hurt.png.import create mode 100644 assets_v2/characters/player/player_idle.png.import create mode 100644 assets_v2/characters/player/player_jump.png.import create mode 100644 assets_v2/characters/player/player_light_attack_1.png.import create mode 100644 assets_v2/characters/player/player_light_attack_2.png.import create mode 100644 assets_v2/characters/player/player_run.png.import create mode 100644 assets_v2/effects/blade_slash.png.import create mode 100644 assets_v2/effects/death_dissolve.png.import create mode 100644 assets_v2/effects/hit.png.import create mode 100644 assets_v2/icons/icon_ghostfire.png.import create mode 100644 assets_v2/icons/icon_obsession.png.import create mode 100644 assets_v2/icons/icon_qi.png.import create mode 100644 assets_v2/icons/icon_songdao.png.import create mode 100644 assets_v2/icons/icon_talisman.png.import create mode 100644 assets_v2/icons/icon_yang.png.import create mode 100644 assets_v2/projectiles/ghost_fire_arrow.png.import diff --git a/assets_v2/characters/gate_warden/gate_warden_attack_1.png.import b/assets_v2/characters/gate_warden/gate_warden_attack_1.png.import new file mode 100644 index 0000000..5c3405a --- /dev/null +++ b/assets_v2/characters/gate_warden/gate_warden_attack_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j6rbty577upi" +path="res://.godot/imported/gate_warden_attack_1.png-55d2c6fc2166d02dc8182be7cb21274a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/gate_warden/gate_warden_attack_1.png" +dest_files=["res://.godot/imported/gate_warden_attack_1.png-55d2c6fc2166d02dc8182be7cb21274a.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=0 diff --git a/assets_v2/characters/gate_warden/gate_warden_attack_2.png.import b/assets_v2/characters/gate_warden/gate_warden_attack_2.png.import new file mode 100644 index 0000000..4d44e65 --- /dev/null +++ b/assets_v2/characters/gate_warden/gate_warden_attack_2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dqdm7po2ghik8" +path="res://.godot/imported/gate_warden_attack_2.png-7679e14f6429408917e92e48a9e4324f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/gate_warden/gate_warden_attack_2.png" +dest_files=["res://.godot/imported/gate_warden_attack_2.png-7679e14f6429408917e92e48a9e4324f.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=0 diff --git a/assets_v2/characters/gate_warden/gate_warden_death.png.import b/assets_v2/characters/gate_warden/gate_warden_death.png.import new file mode 100644 index 0000000..c03281f --- /dev/null +++ b/assets_v2/characters/gate_warden/gate_warden_death.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8441m0s2q81m" +path="res://.godot/imported/gate_warden_death.png-fba36d5833078e9ab2907a8988ab22eb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/gate_warden/gate_warden_death.png" +dest_files=["res://.godot/imported/gate_warden_death.png-fba36d5833078e9ab2907a8988ab22eb.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=0 diff --git a/assets_v2/characters/gate_warden/gate_warden_hurt.png.import b/assets_v2/characters/gate_warden/gate_warden_hurt.png.import new file mode 100644 index 0000000..8dbc9c5 --- /dev/null +++ b/assets_v2/characters/gate_warden/gate_warden_hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1kd54q3hp82d" +path="res://.godot/imported/gate_warden_hurt.png-543808f87028b14f2a25f4ff2f9e6354.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/gate_warden/gate_warden_hurt.png" +dest_files=["res://.godot/imported/gate_warden_hurt.png-543808f87028b14f2a25f4ff2f9e6354.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=0 diff --git a/assets_v2/characters/gate_warden/gate_warden_idle.png.import b/assets_v2/characters/gate_warden/gate_warden_idle.png.import new file mode 100644 index 0000000..cb20c0c --- /dev/null +++ b/assets_v2/characters/gate_warden/gate_warden_idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dx25xv64b07w1" +path="res://.godot/imported/gate_warden_idle.png-3025077d03ca4e33bd39dbef99b3840f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/gate_warden/gate_warden_idle.png" +dest_files=["res://.godot/imported/gate_warden_idle.png-3025077d03ca4e33bd39dbef99b3840f.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=0 diff --git a/assets_v2/characters/gate_warden/gate_warden_walk.png.import b/assets_v2/characters/gate_warden/gate_warden_walk.png.import new file mode 100644 index 0000000..c410e18 --- /dev/null +++ b/assets_v2/characters/gate_warden/gate_warden_walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3cpg18mbw5ly" +path="res://.godot/imported/gate_warden_walk.png-28bfd73c2fa4dcd80e41958860202947.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/gate_warden/gate_warden_walk.png" +dest_files=["res://.godot/imported/gate_warden_walk.png-28bfd73c2fa4dcd80e41958860202947.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=0 diff --git a/assets_v2/characters/ghost_archer/ghost_archer_aim.png.import b/assets_v2/characters/ghost_archer/ghost_archer_aim.png.import new file mode 100644 index 0000000..9a69daf --- /dev/null +++ b/assets_v2/characters/ghost_archer/ghost_archer_aim.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmst5hbssnlno" +path="res://.godot/imported/ghost_archer_aim.png-d2d528df8799bd0b83e71aa4c0f36ea2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/ghost_archer/ghost_archer_aim.png" +dest_files=["res://.godot/imported/ghost_archer_aim.png-d2d528df8799bd0b83e71aa4c0f36ea2.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=0 diff --git a/assets_v2/characters/ghost_archer/ghost_archer_death.png.import b/assets_v2/characters/ghost_archer/ghost_archer_death.png.import new file mode 100644 index 0000000..5c1ee79 --- /dev/null +++ b/assets_v2/characters/ghost_archer/ghost_archer_death.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://utefdtrhsnxn" +path="res://.godot/imported/ghost_archer_death.png-4b527082d473b378704eef605e449f8f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/ghost_archer/ghost_archer_death.png" +dest_files=["res://.godot/imported/ghost_archer_death.png-4b527082d473b378704eef605e449f8f.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=0 diff --git a/assets_v2/characters/ghost_archer/ghost_archer_hurt.png.import b/assets_v2/characters/ghost_archer/ghost_archer_hurt.png.import new file mode 100644 index 0000000..9602030 --- /dev/null +++ b/assets_v2/characters/ghost_archer/ghost_archer_hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg6n2ink626oe" +path="res://.godot/imported/ghost_archer_hurt.png-966e0ef3067991128e1e0faf3d14e49b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/ghost_archer/ghost_archer_hurt.png" +dest_files=["res://.godot/imported/ghost_archer_hurt.png-966e0ef3067991128e1e0faf3d14e49b.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=0 diff --git a/assets_v2/characters/ghost_archer/ghost_archer_idle.png.import b/assets_v2/characters/ghost_archer/ghost_archer_idle.png.import new file mode 100644 index 0000000..5ab45fc --- /dev/null +++ b/assets_v2/characters/ghost_archer/ghost_archer_idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ufhdunh7tx0g" +path="res://.godot/imported/ghost_archer_idle.png-f80c504667d65e8a9049ab2d2e0f13ce.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/ghost_archer/ghost_archer_idle.png" +dest_files=["res://.godot/imported/ghost_archer_idle.png-f80c504667d65e8a9049ab2d2e0f13ce.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=0 diff --git a/assets_v2/characters/ghost_archer/ghost_archer_retreat.png.import b/assets_v2/characters/ghost_archer/ghost_archer_retreat.png.import new file mode 100644 index 0000000..d9cca5c --- /dev/null +++ b/assets_v2/characters/ghost_archer/ghost_archer_retreat.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8cdv28hxq057" +path="res://.godot/imported/ghost_archer_retreat.png-c1593dc723ceaa23c691361b5fff2a75.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/ghost_archer/ghost_archer_retreat.png" +dest_files=["res://.godot/imported/ghost_archer_retreat.png-c1593dc723ceaa23c691361b5fff2a75.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=0 diff --git a/assets_v2/characters/ghost_archer/ghost_archer_shoot.png.import b/assets_v2/characters/ghost_archer/ghost_archer_shoot.png.import new file mode 100644 index 0000000..fc41cd4 --- /dev/null +++ b/assets_v2/characters/ghost_archer/ghost_archer_shoot.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxaee112ws615" +path="res://.godot/imported/ghost_archer_shoot.png-6fbcb7deb382192364effd26ff78fc3c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/ghost_archer/ghost_archer_shoot.png" +dest_files=["res://.godot/imported/ghost_archer_shoot.png-6fbcb7deb382192364effd26ff78fc3c.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=0 diff --git a/assets_v2/characters/melee_ghost/melee_ghost_attack.png.import b/assets_v2/characters/melee_ghost/melee_ghost_attack.png.import new file mode 100644 index 0000000..b6cf12f --- /dev/null +++ b/assets_v2/characters/melee_ghost/melee_ghost_attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cljpnpfd4s2vm" +path="res://.godot/imported/melee_ghost_attack.png-4bb00c0167bce8093773d8260e3c2285.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/melee_ghost/melee_ghost_attack.png" +dest_files=["res://.godot/imported/melee_ghost_attack.png-4bb00c0167bce8093773d8260e3c2285.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=0 diff --git a/assets_v2/characters/melee_ghost/melee_ghost_death.png.import b/assets_v2/characters/melee_ghost/melee_ghost_death.png.import new file mode 100644 index 0000000..4645a6b --- /dev/null +++ b/assets_v2/characters/melee_ghost/melee_ghost_death.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b31o0wt7r06e5" +path="res://.godot/imported/melee_ghost_death.png-87bf7b6f79744fc31ef1890ff456b948.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/melee_ghost/melee_ghost_death.png" +dest_files=["res://.godot/imported/melee_ghost_death.png-87bf7b6f79744fc31ef1890ff456b948.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=0 diff --git a/assets_v2/characters/melee_ghost/melee_ghost_hurt.png.import b/assets_v2/characters/melee_ghost/melee_ghost_hurt.png.import new file mode 100644 index 0000000..97be28b --- /dev/null +++ b/assets_v2/characters/melee_ghost/melee_ghost_hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4tj0t5tdxqa5" +path="res://.godot/imported/melee_ghost_hurt.png-d0650d60650818199caed31e3c50d4e2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/melee_ghost/melee_ghost_hurt.png" +dest_files=["res://.godot/imported/melee_ghost_hurt.png-d0650d60650818199caed31e3c50d4e2.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=0 diff --git a/assets_v2/characters/melee_ghost/melee_ghost_idle.png.import b/assets_v2/characters/melee_ghost/melee_ghost_idle.png.import new file mode 100644 index 0000000..71ec021 --- /dev/null +++ b/assets_v2/characters/melee_ghost/melee_ghost_idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://birc1yirpasb8" +path="res://.godot/imported/melee_ghost_idle.png-dc160e6e8b313fdf23483a6486b07d63.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/melee_ghost/melee_ghost_idle.png" +dest_files=["res://.godot/imported/melee_ghost_idle.png-dc160e6e8b313fdf23483a6486b07d63.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=0 diff --git a/assets_v2/characters/melee_ghost/melee_ghost_walk.png.import b/assets_v2/characters/melee_ghost/melee_ghost_walk.png.import new file mode 100644 index 0000000..6096855 --- /dev/null +++ b/assets_v2/characters/melee_ghost/melee_ghost_walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dkmjswuuwxtb0" +path="res://.godot/imported/melee_ghost_walk.png-276d25bac0480ef7dcf2ea756e8d694a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/melee_ghost/melee_ghost_walk.png" +dest_files=["res://.godot/imported/melee_ghost_walk.png-276d25bac0480ef7dcf2ea756e8d694a.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=0 diff --git a/assets_v2/characters/player/player_death.png.import b/assets_v2/characters/player/player_death.png.import new file mode 100644 index 0000000..162c37a --- /dev/null +++ b/assets_v2/characters/player/player_death.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dt4026i4msp1y" +path="res://.godot/imported/player_death.png-5addb46f63130cb5ee1c23398de3b76f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_death.png" +dest_files=["res://.godot/imported/player_death.png-5addb46f63130cb5ee1c23398de3b76f.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=0 diff --git a/assets_v2/characters/player/player_fall.png.import b/assets_v2/characters/player/player_fall.png.import new file mode 100644 index 0000000..2de7def --- /dev/null +++ b/assets_v2/characters/player/player_fall.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://degl70y52h452" +path="res://.godot/imported/player_fall.png-ca7b6848ca99a61b8a9f92ed678b9120.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_fall.png" +dest_files=["res://.godot/imported/player_fall.png-ca7b6848ca99a61b8a9f92ed678b9120.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=0 diff --git a/assets_v2/characters/player/player_heavy_attack.png.import b/assets_v2/characters/player/player_heavy_attack.png.import new file mode 100644 index 0000000..d06e761 --- /dev/null +++ b/assets_v2/characters/player/player_heavy_attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dijfvtkagpo05" +path="res://.godot/imported/player_heavy_attack.png-65dc856cc0a2e5893ed67236886c8926.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_heavy_attack.png" +dest_files=["res://.godot/imported/player_heavy_attack.png-65dc856cc0a2e5893ed67236886c8926.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=0 diff --git a/assets_v2/characters/player/player_hurt.png.import b/assets_v2/characters/player/player_hurt.png.import new file mode 100644 index 0000000..03cdd06 --- /dev/null +++ b/assets_v2/characters/player/player_hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://74eot40vnbfb" +path="res://.godot/imported/player_hurt.png-6e20f3fc7495cc01fb3bf558ab884d83.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_hurt.png" +dest_files=["res://.godot/imported/player_hurt.png-6e20f3fc7495cc01fb3bf558ab884d83.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=0 diff --git a/assets_v2/characters/player/player_idle.png.import b/assets_v2/characters/player/player_idle.png.import new file mode 100644 index 0000000..8db47d6 --- /dev/null +++ b/assets_v2/characters/player/player_idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dno2lakm1q7nn" +path="res://.godot/imported/player_idle.png-f3a900ae7b18aaae603e94e0f90994ca.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_idle.png" +dest_files=["res://.godot/imported/player_idle.png-f3a900ae7b18aaae603e94e0f90994ca.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=0 diff --git a/assets_v2/characters/player/player_jump.png.import b/assets_v2/characters/player/player_jump.png.import new file mode 100644 index 0000000..6220f1f --- /dev/null +++ b/assets_v2/characters/player/player_jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uagbrjo32t7g" +path="res://.godot/imported/player_jump.png-7e099fa489dc2c2088fff924fbdd469d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_jump.png" +dest_files=["res://.godot/imported/player_jump.png-7e099fa489dc2c2088fff924fbdd469d.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=0 diff --git a/assets_v2/characters/player/player_light_attack_1.png.import b/assets_v2/characters/player/player_light_attack_1.png.import new file mode 100644 index 0000000..68a53e4 --- /dev/null +++ b/assets_v2/characters/player/player_light_attack_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhivsiv1jct5c" +path="res://.godot/imported/player_light_attack_1.png-7c23cbc793659ad75f714443c7c51390.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_light_attack_1.png" +dest_files=["res://.godot/imported/player_light_attack_1.png-7c23cbc793659ad75f714443c7c51390.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=0 diff --git a/assets_v2/characters/player/player_light_attack_2.png.import b/assets_v2/characters/player/player_light_attack_2.png.import new file mode 100644 index 0000000..e247ba7 --- /dev/null +++ b/assets_v2/characters/player/player_light_attack_2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcxjtg8emol8h" +path="res://.godot/imported/player_light_attack_2.png-94e3a934a8524f9da8aeecc1f819f35e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_light_attack_2.png" +dest_files=["res://.godot/imported/player_light_attack_2.png-94e3a934a8524f9da8aeecc1f819f35e.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=0 diff --git a/assets_v2/characters/player/player_run.png.import b/assets_v2/characters/player/player_run.png.import new file mode 100644 index 0000000..5ae7f9f --- /dev/null +++ b/assets_v2/characters/player/player_run.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd2c7ln422rmx" +path="res://.godot/imported/player_run.png-c433a47e7243b632ae312a431c5e09db.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/characters/player/player_run.png" +dest_files=["res://.godot/imported/player_run.png-c433a47e7243b632ae312a431c5e09db.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=0 diff --git a/assets_v2/effects/blade_slash.png.import b/assets_v2/effects/blade_slash.png.import new file mode 100644 index 0000000..70b24e9 --- /dev/null +++ b/assets_v2/effects/blade_slash.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gdroiq70dtrq" +path="res://.godot/imported/blade_slash.png-3741e87deef95279eb3b52be03bf1db3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/effects/blade_slash.png" +dest_files=["res://.godot/imported/blade_slash.png-3741e87deef95279eb3b52be03bf1db3.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=0 diff --git a/assets_v2/effects/death_dissolve.png.import b/assets_v2/effects/death_dissolve.png.import new file mode 100644 index 0000000..f96a2f9 --- /dev/null +++ b/assets_v2/effects/death_dissolve.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2euomx6hqvmr" +path="res://.godot/imported/death_dissolve.png-da26d00dcede2c530dad81da82ae2857.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/effects/death_dissolve.png" +dest_files=["res://.godot/imported/death_dissolve.png-da26d00dcede2c530dad81da82ae2857.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=0 diff --git a/assets_v2/effects/hit.png.import b/assets_v2/effects/hit.png.import new file mode 100644 index 0000000..03354e8 --- /dev/null +++ b/assets_v2/effects/hit.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bk4ibkefdygf" +path="res://.godot/imported/hit.png-9d767548c80c50bf28c1dc0c91212aa8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/effects/hit.png" +dest_files=["res://.godot/imported/hit.png-9d767548c80c50bf28c1dc0c91212aa8.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=0 diff --git a/assets_v2/icons/icon_ghostfire.png.import b/assets_v2/icons/icon_ghostfire.png.import new file mode 100644 index 0000000..5feb75b --- /dev/null +++ b/assets_v2/icons/icon_ghostfire.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvhxkblt8ioay" +path="res://.godot/imported/icon_ghostfire.png-702e2140ea0f25e29c07fc3292edab97.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/icons/icon_ghostfire.png" +dest_files=["res://.godot/imported/icon_ghostfire.png-702e2140ea0f25e29c07fc3292edab97.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=0 diff --git a/assets_v2/icons/icon_obsession.png.import b/assets_v2/icons/icon_obsession.png.import new file mode 100644 index 0000000..6092c3a --- /dev/null +++ b/assets_v2/icons/icon_obsession.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://eie1xexltsff" +path="res://.godot/imported/icon_obsession.png-033b7bd6686c8ec3615b954972ca2f8c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/icons/icon_obsession.png" +dest_files=["res://.godot/imported/icon_obsession.png-033b7bd6686c8ec3615b954972ca2f8c.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=0 diff --git a/assets_v2/icons/icon_qi.png.import b/assets_v2/icons/icon_qi.png.import new file mode 100644 index 0000000..c8ab168 --- /dev/null +++ b/assets_v2/icons/icon_qi.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bt7876vq0rbpo" +path="res://.godot/imported/icon_qi.png-4aeddc91b381f3565842788346310541.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/icons/icon_qi.png" +dest_files=["res://.godot/imported/icon_qi.png-4aeddc91b381f3565842788346310541.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=0 diff --git a/assets_v2/icons/icon_songdao.png.import b/assets_v2/icons/icon_songdao.png.import new file mode 100644 index 0000000..ceeb0c3 --- /dev/null +++ b/assets_v2/icons/icon_songdao.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckh78856gf7vy" +path="res://.godot/imported/icon_songdao.png-983974d374eae7380e182341491aaae3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/icons/icon_songdao.png" +dest_files=["res://.godot/imported/icon_songdao.png-983974d374eae7380e182341491aaae3.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=0 diff --git a/assets_v2/icons/icon_talisman.png.import b/assets_v2/icons/icon_talisman.png.import new file mode 100644 index 0000000..210a013 --- /dev/null +++ b/assets_v2/icons/icon_talisman.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxtrimjtoer1b" +path="res://.godot/imported/icon_talisman.png-eb0392b04f496bb6266bf2daf30e51e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/icons/icon_talisman.png" +dest_files=["res://.godot/imported/icon_talisman.png-eb0392b04f496bb6266bf2daf30e51e8.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=0 diff --git a/assets_v2/icons/icon_yang.png.import b/assets_v2/icons/icon_yang.png.import new file mode 100644 index 0000000..59e34dd --- /dev/null +++ b/assets_v2/icons/icon_yang.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0h1wuutul0ou" +path="res://.godot/imported/icon_yang.png-aec7b01804e56686be9b78f012623620.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/icons/icon_yang.png" +dest_files=["res://.godot/imported/icon_yang.png-aec7b01804e56686be9b78f012623620.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=0 diff --git a/assets_v2/projectiles/ghost_fire_arrow.png.import b/assets_v2/projectiles/ghost_fire_arrow.png.import new file mode 100644 index 0000000..ca6063c --- /dev/null +++ b/assets_v2/projectiles/ghost_fire_arrow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dn4xo57ducjhg" +path="res://.godot/imported/ghost_fire_arrow.png-70dea4f205556bd0adda7db065ce6d59.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets_v2/projectiles/ghost_fire_arrow.png" +dest_files=["res://.godot/imported/ghost_fire_arrow.png-70dea4f205556bd0adda7db065ce6d59.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=0 From 0905089e41f680cb4691349f59494b87bee4f7ea Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:28 +1000 Subject: [PATCH 2/9] [M1.5][Codex] Integrate player animations and attack frames --- actors/player/player.tscn | 44 ++++++++++++++++----- actors/player/states/airborne_state.gd | 3 +- actors/player/states/attacking_state.gd | 51 +++++++++++-------------- actors/player/states/dead_state.gd | 2 +- actors/player/states/grounded_state.gd | 4 +- actors/player/states/hurt_state.gd | 2 +- ui/result_screen.gd | 8 ++++ visuals/death_effect.gd | 28 ++++++++++++++ visuals/player_slash_visual.gd | 34 +++++++++++++++++ 9 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 visuals/death_effect.gd create mode 100644 visuals/player_slash_visual.gd diff --git a/actors/player/player.tscn b/actors/player/player.tscn index db804e7..5284592 100644 --- a/actors/player/player.tscn +++ b/actors/player/player.tscn @@ -1,15 +1,19 @@ [gd_scene load_steps=8 format=3 uid="uid://bnnplayer0001"] [ext_resource type="Script" path="res://actors/player/player.gd" id="1_player"] -[ext_resource type="SpriteFrames" path="res://actors/player/player_frames.tres" id="2_frames"] +[ext_resource type="SpriteFrames" path="res://assets_v2/godot/player_frames.tres" id="2_frames"] [ext_resource type="Script" path="res://core/hurtbox.gd" id="3_hurtbox"] [ext_resource type="Script" path="res://core/hitbox.gd" id="4_hitbox"] +[ext_resource type="Script" path="res://visuals/player_slash_visual.gd" id="5_slash"] +[ext_resource type="Texture2D" path="res://assets_v2/effects/blade_slash.png" id="6_slash_texture"] +[ext_resource type="Script" path="res://visuals/death_effect.gd" id="7_death_effect"] +[ext_resource type="Texture2D" path="res://assets_v2/effects/death_dissolve.png" id="8_death_texture"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_body"] -size = Vector2(14, 26) +size = Vector2(18, 44) [sub_resource type="RectangleShape2D" id="RectangleShape2D_hurt"] -size = Vector2(16, 28) +size = Vector2(24, 52) [sub_resource type="RectangleShape2D" id="RectangleShape2D_hit"] size = Vector2(24, 20) @@ -20,13 +24,33 @@ collision_mask = 1 script = ExtResource("1_player") [node name="Sprite" type="AnimatedSprite2D" parent="."] -position = Vector2(0, -24) sprite_frames = ExtResource("2_frames") -animation = &"idle" -autoplay = "idle" +animation = &"player_idle" +autoplay = "player_idle" +centered = false +offset = Vector2(-48, -88) + +[node name="SlashVisual" type="Sprite2D" parent="."] +position = Vector2(18, -48) +texture = ExtResource("6_slash_texture") +hframes = 6 +visible = false +z_index = 2 +script = ExtResource("5_slash") +playback_fps = 18.0 + +[node name="DeathEffect" type="Sprite2D" parent="."] +position = Vector2(0, -44) +texture = ExtResource("8_death_texture") +hframes = 8 +visible = false +z_index = 3 +modulate = Color(1, 1, 1, 0.72) +script = ExtResource("7_death_effect") +playback_fps = 8.0 [node name="Body" type="CollisionShape2D" parent="."] -position = Vector2(0, -13) +position = Vector2(0, -22) shape = SubResource("RectangleShape2D_body") [node name="Hurtbox" type="Area2D" parent="."] @@ -35,7 +59,7 @@ collision_mask = 0 script = ExtResource("3_hurtbox") [node name="Shape" type="CollisionShape2D" parent="Hurtbox"] -position = Vector2(0, -14) +position = Vector2(0, -26) shape = SubResource("RectangleShape2D_hurt") [node name="AttackHitbox" type="Area2D" parent="."] @@ -44,10 +68,10 @@ collision_mask = 16 script = ExtResource("4_hitbox") [node name="Shape" type="CollisionShape2D" parent="AttackHitbox"] -position = Vector2(18, -14) +position = Vector2(28, -38) shape = SubResource("RectangleShape2D_hit") [node name="Camera" type="Camera2D" parent="."] -position = Vector2(0, -24) +position = Vector2(0, -44) position_smoothing_enabled = true position_smoothing_speed = 6.0 diff --git a/actors/player/states/airborne_state.gd b/actors/player/states/airborne_state.gd index 49617ad..7ba0e43 100644 --- a/actors/player/states/airborne_state.gd +++ b/actors/player/states/airborne_state.gd @@ -7,11 +7,12 @@ func id() -> StringName: return AIRBORNE func enter() -> void: - player.play(&"jump") + player.play(&"player_jump") func physics_update(delta: float) -> StringName: player.apply_gravity(delta) player.apply_horizontal_input(player.balance().player_air_control) + player.play(&"player_jump" if player.velocity.y < 0.0 else &"player_fall") if wants_attack(): return ATTACKING diff --git a/actors/player/states/attacking_state.gd b/actors/player/states/attacking_state.gd index 7246e86..3a613c6 100644 --- a/actors/player/states/attacking_state.gd +++ b/actors/player/states/attacking_state.gd @@ -1,27 +1,25 @@ class_name AttackingState extends PlayerState -## Light attack: wind-up, active window, recovery. Timings and the stamina cost -## (0 by design — see research report 6.1) come from BalanceConfig. -## -## The hitbox is only live during the active window, so the swing's reach in -## time is data, not an animation coincidence. +## V2 light attack. The authored frame map is authoritative: +## startup 0–3, active 4–5, recovery 6–7. -enum Phase { WINDUP, ACTIVE, RECOVERY } - -var _phase: int = Phase.WINDUP -var _elapsed: float = 0.0 +const ACTIVE_FIRST_FRAME := 4 +const ACTIVE_LAST_FRAME := 5 func id() -> StringName: return ATTACKING func enter() -> void: - _phase = Phase.WINDUP - _elapsed = 0.0 - player.play(&"attack") + player.attack_hitbox.set_active(false) + if not player.sprite.frame_changed.is_connected(_sync_attack_window): + player.sprite.frame_changed.connect(_sync_attack_window) + player.play(&"player_light_attack_1") player.sprite.frame = 0 player.spend_stamina(player.balance().light_attack_stamina_cost) func exit() -> void: + if player.sprite.frame_changed.is_connected(_sync_attack_window): + player.sprite.frame_changed.disconnect(_sync_attack_window) player.attack_hitbox.set_active(false) func physics_update(delta: float) -> StringName: @@ -29,21 +27,16 @@ func physics_update(delta: float) -> StringName: # Committed: the swing does not steer, but momentum is bled off so the # player does not slide through the whole animation. player.decelerate(delta) - _elapsed += delta - - var balance := player.balance() - match _phase: - Phase.WINDUP: - if _elapsed >= balance.light_attack_windup: - _phase = Phase.ACTIVE - _elapsed = 0.0 - player.attack_hitbox.set_active(true) - Phase.ACTIVE: - if _elapsed >= balance.light_attack_active: - _phase = Phase.RECOVERY - _elapsed = 0.0 - player.attack_hitbox.set_active(false) - Phase.RECOVERY: - if _elapsed >= balance.light_attack_recovery: - return GROUNDED if player.is_on_floor() else AIRBORNE + _sync_attack_window() + if player.animation_finished(): + return GROUNDED if player.is_on_floor() else AIRBORNE return &"" + + +func _sync_attack_window() -> void: + var active := ( + player.sprite.animation == &"player_light_attack_1" + and player.sprite.frame >= ACTIVE_FIRST_FRAME + and player.sprite.frame <= ACTIVE_LAST_FRAME + ) + player.attack_hitbox.set_active(active) diff --git a/actors/player/states/dead_state.gd b/actors/player/states/dead_state.gd index e076696..d08b2b0 100644 --- a/actors/player/states/dead_state.gd +++ b/actors/player/states/dead_state.gd @@ -9,7 +9,7 @@ func enter() -> void: player.input_enabled = false player.velocity = Vector2.ZERO player.attack_hitbox.set_active(false) - player.play(&"death") + player.play(&"player_death") player.sprite.frame = 0 func physics_update(delta: float) -> StringName: diff --git a/actors/player/states/grounded_state.gd b/actors/player/states/grounded_state.gd index 2a194f4..7683955 100644 --- a/actors/player/states/grounded_state.gd +++ b/actors/player/states/grounded_state.gd @@ -6,7 +6,7 @@ func id() -> StringName: return GROUNDED func enter() -> void: - player.play(&"idle") + player.play(&"player_idle") func physics_update(delta: float) -> StringName: player.apply_gravity(delta) @@ -20,5 +20,5 @@ func physics_update(delta: float) -> StringName: if not player.is_on_floor(): return AIRBORNE - player.play(&"run" if player.input_direction() != 0 else &"idle") + player.play(&"player_run" if player.input_direction() != 0 else &"player_idle") return &"" diff --git a/actors/player/states/hurt_state.gd b/actors/player/states/hurt_state.gd index 8cdbf7d..8066ecc 100644 --- a/actors/player/states/hurt_state.gd +++ b/actors/player/states/hurt_state.gd @@ -10,7 +10,7 @@ func id() -> StringName: func enter() -> void: _remaining = player.balance().player_hurt_duration - player.play(&"hurt") + player.play(&"player_hurt") player.sprite.frame = 0 func physics_update(delta: float) -> StringName: diff --git a/ui/result_screen.gd b/ui/result_screen.gd index d71ef2c..3d459a9 100644 --- a/ui/result_screen.gd +++ b/ui/result_screen.gd @@ -22,6 +22,14 @@ func _ready() -> void: func show_outcome(outcome: StringName) -> void: var state := _coordinator.state() var victory := outcome == RunState.STATUS_VICTORY + if not victory: + var player := _coordinator.player() + if ( + player != null + and player.sprite.animation == &"player_death" + and player.sprite.is_playing() + ): + await player.sprite.animation_finished # Not 同归: the same-death mechanic is not implemented in M1, so a win here # is an ordinary victory and the screen must not claim otherwise. _title.text = "VICTORY" if victory else "DEATH" diff --git a/visuals/death_effect.gd b/visuals/death_effect.gd new file mode 100644 index 0000000..a11d15d --- /dev/null +++ b/visuals/death_effect.gd @@ -0,0 +1,28 @@ +class_name DeathEffect +extends Sprite2D +## Optional overlay that accompanies, but never replaces, an actor's authored +## death animation. The actor owns death timing and removal. + +@export var playback_fps: float = 8.0 + +var _playing: bool = false +var _finished: bool = false +var _elapsed: float = 0.0 + + +func _process(delta: float) -> void: + var actor := get_parent() + if actor == null or not actor.has_method("is_dead"): + return + if not _playing and not _finished and bool(actor.call("is_dead")): + _playing = true + visible = true + frame = 0 + if not _playing: + return + _elapsed += delta + frame = mini(int(_elapsed * playback_fps), hframes - 1) + if _elapsed >= float(hframes) / playback_fps: + _playing = false + _finished = true + visible = false diff --git a/visuals/player_slash_visual.gd b/visuals/player_slash_visual.gd new file mode 100644 index 0000000..ee6c90e --- /dev/null +++ b/visuals/player_slash_visual.gd @@ -0,0 +1,34 @@ +class_name PlayerSlashVisual +extends Sprite2D +## Read-only visual follower for the existing player light attack. It never +## applies damage; the existing Hitbox and CombatResolver remain authoritative. + +@export var playback_fps: float = 18.0 + +var _playing: bool = false +var _elapsed: float = 0.0 + + +func _process(delta: float) -> void: + var player := get_parent() as Player + if player == null: + visible = false + return + flip_h = player.facing() < 0 + position.x = absf(position.x) * player.facing() + if ( + not _playing + and player.sprite.animation == &"player_light_attack_1" + and player.sprite.frame == 4 + ): + _playing = true + _elapsed = 0.0 + frame = 0 + visible = true + if not _playing: + return + _elapsed += delta + frame = mini(int(_elapsed * playback_fps), hframes - 1) + if _elapsed >= float(hframes) / playback_fps: + _playing = false + visible = false From 22831630152dcaa44f4ade9446fe90ad774f96d2 Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:32 +1000 Subject: [PATCH 3/9] [M1.5][Codex] Integrate melee ghost animations --- actors/enemies/ghost_melee.tscn | 46 +++++++++++++++++-- actors/enemies/melee_ghost.gd | 81 +++++++++++++++++++++++++++++++++ data/actors/ghost_melee.tres | 8 ++-- 3 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 actors/enemies/melee_ghost.gd diff --git a/actors/enemies/ghost_melee.tscn b/actors/enemies/ghost_melee.tscn index 6dd41d2..92f3a25 100644 --- a/actors/enemies/ghost_melee.tscn +++ b/actors/enemies/ghost_melee.tscn @@ -1,11 +1,49 @@ -[gd_scene load_steps=3 format=3 uid="uid://bnnghostmelee01"] +[gd_scene load_steps=8 format=3 uid="uid://bnnghostmelee01"] [ext_resource type="PackedScene" path="res://actors/enemies/enemy_base.tscn" id="1_base"] -[ext_resource type="SpriteFrames" path="res://actors/enemies/ghost_melee_frames.tres" id="2_frames"] +[ext_resource type="SpriteFrames" path="res://assets_v2/godot/melee_ghost_frames.tres" id="2_frames"] +[ext_resource type="Script" path="res://actors/enemies/melee_ghost.gd" id="3_script"] +[ext_resource type="Script" path="res://visuals/death_effect.gd" id="4_death_effect"] +[ext_resource type="Texture2D" path="res://assets_v2/effects/death_dissolve.png" id="5_death_texture"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_v2_body"] +size = Vector2(22, 48) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_v2_hurt"] +size = Vector2(28, 56) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_v2_hit"] +size = Vector2(46, 30) [node name="GhostMelee" instance=ExtResource("1_base")] +script = ExtResource("3_script") [node name="Sprite" parent="." index="0"] sprite_frames = ExtResource("2_frames") -animation = &"idle" -autoplay = "idle" +animation = &"melee_ghost_idle" +autoplay = "melee_ghost_idle" +position = Vector2(0, 0) +centered = false +offset = Vector2(-48, -90) + +[node name="Body" parent="." index="1"] +position = Vector2(0, -24) +shape = SubResource("RectangleShape2D_v2_body") + +[node name="Shape" parent="Hurtbox" index="0"] +position = Vector2(0, -28) +shape = SubResource("RectangleShape2D_v2_hurt") + +[node name="Shape" parent="AttackHitbox" index="0"] +position = Vector2(30, -36) +shape = SubResource("RectangleShape2D_v2_hit") + +[node name="DeathEffect" type="Sprite2D" parent="." index="4"] +position = Vector2(0, -45) +texture = ExtResource("5_death_texture") +hframes = 8 +visible = false +z_index = 3 +modulate = Color(1, 1, 1, 0.72) +script = ExtResource("4_death_effect") +playback_fps = 8.0 diff --git a/actors/enemies/melee_ghost.gd b/actors/enemies/melee_ghost.gd new file mode 100644 index 0000000..b337aa8 --- /dev/null +++ b/actors/enemies/melee_ghost.gd @@ -0,0 +1,81 @@ +class_name MeleeGhost +extends EnemyBase +## V2 visual specialization of EnemyBase. The existing AI states and damage +## path remain intact; authored animation frames only gate the existing hitbox. + +const ACTIVE_FIRST_FRAME := 4 +const ACTIVE_LAST_FRAME := 5 + + +func _ready() -> void: + super._ready() + sprite.frame_changed.connect(_sync_attack_window) + + +func _enter_state(next: int) -> void: + match next: + State.IDLE: + _play(&"melee_ghost_idle") + State.CHASE: + _play(&"melee_ghost_walk") + State.WINDUP: + attack_hitbox.set_active(false) + sprite.modulate = Color.WHITE + sprite.play(&"melee_ghost_attack") + sprite.frame = 0 + State.ATTACK: + _sync_attack_window() + State.RECOVER: + attack_hitbox.set_active(false) + State.HURT: + attack_hitbox.set_active(false) + sprite.play(&"melee_ghost_hurt") + sprite.frame = 0 + State.DEAD: + attack_hitbox.set_active(false) + sprite.play(&"melee_ghost_death") + sprite.frame = 0 + + +func _exit_state(_previous: int) -> void: + attack_hitbox.set_active(false) + + +func _update_state(delta: float) -> void: + match _state: + State.IDLE: + _decelerate(delta) + if _target_in_range(config.detection_range): + change_state(State.CHASE) + State.CHASE: + _chase(delta) + State.WINDUP: + _decelerate(delta) + _face_target() + if sprite.frame >= ACTIVE_FIRST_FRAME: + change_state(State.ATTACK) + State.ATTACK: + _decelerate(delta) + if sprite.frame > ACTIVE_LAST_FRAME: + change_state(State.RECOVER) + State.RECOVER: + _decelerate(delta) + if not sprite.is_playing(): + _cooldown = config.attack_cooldown + change_state(State.IDLE) + State.HURT: + _decelerate(delta, 400.0) + if not sprite.is_playing(): + change_state(State.CHASE if _target_in_range(config.detection_range) else State.IDLE) + State.DEAD: + pass + + +func _sync_attack_window() -> void: + var active := ( + not _is_dead + and sprite.animation == &"melee_ghost_attack" + and sprite.frame >= ACTIVE_FIRST_FRAME + and sprite.frame <= ACTIVE_LAST_FRAME + ) + attack_hitbox.set_active(active) diff --git a/data/actors/ghost_melee.tres b/data/actors/ghost_melee.tres index 7e45dde..69f5ec1 100644 --- a/data/actors/ghost_melee.tres +++ b/data/actors/ghost_melee.tres @@ -17,9 +17,9 @@ preferred_gap = 34.0 attack_damage = 9.0 attack_skill_multiplier = 1.0 attack_windup = 0.4 -attack_active = 0.15 -attack_recovery = 0.6 +attack_active = 0.2 +attack_recovery = 0.2 attack_cooldown = 0.35 -hurt_duration = 0.18 -death_duration = 0.5 +hurt_duration = 0.4 +death_duration = 1.05 telegraph_tint = Color(1.7, 1.1, 1, 1) From 0481ffb9b0d11fe5ab6e8aba0279d148ac48bea9 Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:36 +1000 Subject: [PATCH 4/9] [M1.5][Codex] Integrate Ghost Archer and arrow visuals --- actors/enemies/ghost_archer.gd | 37 ++++++++++++++++++++---- actors/enemies/ghost_archer.tscn | 41 ++++++++++++++++++++++++--- actors/enemies/ghost_arrow.gd | 9 ++++++ actors/enemies/ghost_arrow.tscn | 7 ++++- data/actors/ghost_archer.tres | 10 +++---- data/actors/ghost_archer_tactics.tres | 2 +- 6 files changed, 89 insertions(+), 17 deletions(-) diff --git a/actors/enemies/ghost_archer.gd b/actors/enemies/ghost_archer.gd index 62d335a..f06ea5d 100644 --- a/actors/enemies/ghost_archer.gd +++ b/actors/enemies/ghost_archer.gd @@ -10,6 +10,7 @@ signal arrow_fired(arrow: GhostArrow) var _patrol_origin_x: float var _patrol_direction: int = 1 +var _arrow_released: bool = false func _ready() -> void: @@ -25,10 +26,28 @@ func initialize(enemy_config: EnemyConfig, target: Node2D) -> void: func _enter_state(next: int) -> void: - super._enter_state(next) - if next == State.ATTACK: - attack_hitbox.set_active(false) - _shoot() + attack_hitbox.set_active(false) + match next: + State.IDLE: + sprite.play(&"ghost_archer_idle") + State.CHASE: + sprite.play(&"ghost_archer_retreat") + State.WINDUP: + sprite.modulate = Color.WHITE + sprite.play(&"ghost_archer_aim") + sprite.frame = 0 + State.ATTACK: + _arrow_released = false + sprite.play(&"ghost_archer_shoot") + sprite.frame = 0 + State.RECOVER: + pass + State.HURT: + sprite.play(&"ghost_archer_hurt") + sprite.frame = 0 + State.DEAD: + sprite.play(&"ghost_archer_death") + sprite.frame = 0 func _update_state(delta: float) -> void: @@ -42,11 +61,17 @@ func _update_state(delta: float) -> void: State.WINDUP: _decelerate(delta) _face_target() - if _state_elapsed >= config.attack_windup: + # frame_map.json: the bow is fully drawn on aim frame 5. + if sprite.frame >= 5 or not sprite.is_playing(): change_state(State.ATTACK) State.ATTACK: _decelerate(delta) - if _state_elapsed >= config.attack_active: + _face_target() + # frame_map.json: release exactly on shoot frame 1. + if not _arrow_released and sprite.frame >= 1: + _arrow_released = true + _shoot() + if not sprite.is_playing(): change_state(State.RECOVER) State.RECOVER: _decelerate(delta) diff --git a/actors/enemies/ghost_archer.tscn b/actors/enemies/ghost_archer.tscn index 3783d97..049bab5 100644 --- a/actors/enemies/ghost_archer.tscn +++ b/actors/enemies/ghost_archer.tscn @@ -1,9 +1,20 @@ -[gd_scene load_steps=5 format=3] +[gd_scene load_steps=10 format=3] [ext_resource type="PackedScene" path="res://actors/enemies/enemy_base.tscn" id="1_base"] [ext_resource type="Script" path="res://actors/enemies/ghost_archer.gd" id="2_script"] -[ext_resource type="SpriteFrames" path="res://actors/enemies/ghost_archer_frames.tres" id="3_frames"] +[ext_resource type="SpriteFrames" path="res://assets_v2/godot/ghost_archer_frames.tres" id="3_frames"] [ext_resource type="Resource" path="res://data/actors/ghost_archer_tactics.tres" id="4_tactics"] +[ext_resource type="Script" path="res://visuals/death_effect.gd" id="5_death_effect"] +[ext_resource type="Texture2D" path="res://assets_v2/effects/death_dissolve.png" id="6_death_texture"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_v2_body"] +size = Vector2(20, 48) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_v2_hurt"] +size = Vector2(26, 56) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_v2_hit"] +size = Vector2(1, 1) [node name="GhostArcher" instance=ExtResource("1_base")] script = ExtResource("2_script") @@ -11,8 +22,30 @@ archer_config = ExtResource("4_tactics") [node name="Sprite" parent="." index="0"] sprite_frames = ExtResource("3_frames") -animation = &"idle" -autoplay = "idle" +animation = &"ghost_archer_idle" +autoplay = "ghost_archer_idle" +position = Vector2(0, 0) +centered = false +offset = Vector2(-48, -88) + +[node name="Body" parent="." index="1"] +position = Vector2(0, -24) +shape = SubResource("RectangleShape2D_v2_body") + +[node name="Shape" parent="Hurtbox" index="0"] +position = Vector2(0, -28) +shape = SubResource("RectangleShape2D_v2_hurt") [node name="Shape" parent="AttackHitbox" index="0"] disabled = true +shape = SubResource("RectangleShape2D_v2_hit") + +[node name="DeathEffect" type="Sprite2D" parent="." index="4"] +position = Vector2(0, -44) +texture = ExtResource("6_death_texture") +hframes = 8 +visible = false +z_index = 3 +modulate = Color(1, 1, 1, 0.72) +script = ExtResource("5_death_effect") +playback_fps = 8.0 diff --git a/actors/enemies/ghost_arrow.gd b/actors/enemies/ghost_arrow.gd index 3289177..3da6f0c 100644 --- a/actors/enemies/ghost_arrow.gd +++ b/actors/enemies/ghost_arrow.gd @@ -19,6 +19,10 @@ var _configured: bool = false func _ready() -> void: area_entered.connect(_on_area_entered) body_entered.connect(_on_body_entered) + var visual := get_node_or_null(^"Visual") as Sprite2D + var fallback := get_node_or_null(^"FallbackVisual") as CanvasItem + if fallback != null: + fallback.visible = visual == null or visual.texture == null func configure( @@ -54,6 +58,11 @@ func has_impacted() -> bool: return _has_impacted +func uses_v2_texture() -> bool: + var visual := get_node_or_null(^"Visual") as Sprite2D + return visual != null and visual.texture != null + + func _on_area_entered(area: Area2D) -> void: if _has_impacted or not (area is Hurtbox): return diff --git a/actors/enemies/ghost_arrow.tscn b/actors/enemies/ghost_arrow.tscn index 833ff09..1439eab 100644 --- a/actors/enemies/ghost_arrow.tscn +++ b/actors/enemies/ghost_arrow.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=3 format=3] +[gd_scene load_steps=4 format=3] [ext_resource type="Script" path="res://actors/enemies/ghost_arrow.gd" id="1_arrow"] +[ext_resource type="Texture2D" path="res://assets_v2/projectiles/ghost_fire_arrow.png" id="2_arrow_texture"] [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_arrow"] radius = 2.0 @@ -11,9 +12,13 @@ collision_layer = 64 collision_mask = 9 script = ExtResource("1_arrow") +[node name="Visual" type="Sprite2D" parent="."] +texture = ExtResource("2_arrow_texture") + [node name="FallbackVisual" type="Polygon2D" parent="."] polygon = PackedVector2Array(-7, -1, 4, -1, 4, -3, 8, 0, 4, 3, 4, 1, -7, 1) color = Color(0.31, 0.88, 0.65, 0.9) +visible = false [node name="Shape" type="CollisionShape2D" parent="."] rotation = 1.5708 diff --git a/data/actors/ghost_archer.tres b/data/actors/ghost_archer.tres index f2246e8..05f009c 100644 --- a/data/actors/ghost_archer.tres +++ b/data/actors/ghost_archer.tres @@ -16,10 +16,10 @@ attack_range = 220.0 preferred_gap = 150.0 attack_damage = 8.0 attack_skill_multiplier = 1.0 -attack_windup = 0.55 -attack_active = 0.12 -attack_recovery = 0.38 +attack_windup = 0.75 +attack_active = 0.286 +attack_recovery = 0.05 attack_cooldown = 0.85 -hurt_duration = 0.18 -death_duration = 0.5 +hurt_duration = 0.4 +death_duration = 1.05 telegraph_tint = Color(1.15, 1.75, 1.45, 1) diff --git a/data/actors/ghost_archer_tactics.tres b/data/actors/ghost_archer_tactics.tres index 5db18f8..bab6344 100644 --- a/data/actors/ghost_archer_tactics.tres +++ b/data/actors/ghost_archer_tactics.tres @@ -12,4 +12,4 @@ retreat_distance = 92.0 projectile_scene = ExtResource("2_projectile") projectile_speed = 190.0 projectile_lifetime = 2.4 -projectile_spawn_offset = Vector2(18, -22) +projectile_spawn_offset = Vector2(24, -48) From 25dca24481edc20f30b32f717fc79c65bc6128c7 Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:40 +1000 Subject: [PATCH 5/9] [M1.5][Codex] Integrate Gate Warden animations --- actors/boss/boss.tscn | 36 +++++++---- actors/boss/boss_actor.gd | 92 ++++++++++++++++++++++++++++- data/actors/boss_gate_guardian.tres | 10 ++-- 3 files changed, 121 insertions(+), 17 deletions(-) diff --git a/actors/boss/boss.tscn b/actors/boss/boss.tscn index 4eaeb2a..170c628 100644 --- a/actors/boss/boss.tscn +++ b/actors/boss/boss.tscn @@ -1,18 +1,20 @@ -[gd_scene load_steps=8 format=3 uid="uid://bnnbossgate001"] +[gd_scene load_steps=10 format=3 uid="uid://bnnbossgate001"] [ext_resource type="Script" path="res://actors/boss/boss_actor.gd" id="1_boss"] -[ext_resource type="SpriteFrames" path="res://actors/boss/boss_frames.tres" id="2_frames"] +[ext_resource type="SpriteFrames" path="res://assets_v2/godot/gate_warden_frames.tres" id="2_frames"] [ext_resource type="Script" path="res://core/hurtbox.gd" id="3_hurtbox"] [ext_resource type="Script" path="res://core/hitbox.gd" id="4_hitbox"] +[ext_resource type="Script" path="res://visuals/death_effect.gd" id="5_death_effect"] +[ext_resource type="Texture2D" path="res://assets_v2/effects/death_dissolve.png" id="6_death_texture"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_body"] -size = Vector2(30, 52) +size = Vector2(54, 104) [sub_resource type="RectangleShape2D" id="RectangleShape2D_hurt"] -size = Vector2(38, 56) +size = Vector2(70, 120) [sub_resource type="RectangleShape2D" id="RectangleShape2D_hit"] -size = Vector2(46, 40) +size = Vector2(110, 70) [node name="Boss" type="CharacterBody2D"] collision_layer = 4 @@ -20,13 +22,14 @@ collision_mask = 1 script = ExtResource("1_boss") [node name="Sprite" type="AnimatedSprite2D" parent="."] -position = Vector2(0, -48) sprite_frames = ExtResource("2_frames") -animation = &"idle" -autoplay = "idle" +animation = &"gate_warden_idle" +autoplay = "gate_warden_idle" +centered = false +offset = Vector2(-96, -176) [node name="Body" type="CollisionShape2D" parent="."] -position = Vector2(0, -26) +position = Vector2(0, -52) shape = SubResource("RectangleShape2D_body") [node name="Hurtbox" type="Area2D" parent="."] @@ -35,7 +38,7 @@ collision_mask = 0 script = ExtResource("3_hurtbox") [node name="Shape" type="CollisionShape2D" parent="Hurtbox"] -position = Vector2(0, -28) +position = Vector2(0, -60) shape = SubResource("RectangleShape2D_hurt") [node name="AttackHitbox" type="Area2D" parent="."] @@ -44,5 +47,16 @@ collision_mask = 8 script = ExtResource("4_hitbox") [node name="Shape" type="CollisionShape2D" parent="AttackHitbox"] -position = Vector2(34, -26) +position = Vector2(66, -58) shape = SubResource("RectangleShape2D_hit") + +[node name="DeathEffect" type="Sprite2D" parent="."] +position = Vector2(0, -88) +scale = Vector2(2, 2) +texture = ExtResource("6_death_texture") +hframes = 8 +visible = false +z_index = 3 +modulate = Color(1, 1, 1, 0.72) +script = ExtResource("5_death_effect") +playback_fps = 8.0 diff --git a/actors/boss/boss_actor.gd b/actors/boss/boss_actor.gd index ba211ef..234d2f1 100644 --- a/actors/boss/boss_actor.gd +++ b/actors/boss/boss_actor.gd @@ -19,13 +19,103 @@ extends EnemyBase signal defeated(boss: BossActor) +const ACTIVE_FIRST_FRAME := 4 +const ACTIVE_LAST_FRAME := 5 + +var _death_source_id: StringName = &"unknown" +var _boss_death_published: bool = false + + +func _ready() -> void: + super._ready() + sprite.animation_finished.connect(_on_animation_finished) + sprite.frame_changed.connect(_sync_attack_window) + func start_encounter() -> void: EventBus.boss_started.emit( EventBus.context({"actor_id": actor_id(), "max_hp": max_hp()}) ) func _publish_death(source_id: StringName) -> void: + # EnemyBase owns idempotent death. Completion waits for the authored + # collapse so the victory screen cannot cover its first frame. + _death_source_id = source_id + + +func _enter_state(next: int) -> void: + match next: + State.IDLE: + sprite.play(&"gate_warden_idle") + State.CHASE: + sprite.play(&"gate_warden_walk") + State.WINDUP: + attack_hitbox.set_active(false) + sprite.modulate = Color.WHITE + sprite.play(&"gate_warden_attack_1") + sprite.frame = 0 + State.ATTACK: + _sync_attack_window() + State.RECOVER: + attack_hitbox.set_active(false) + State.HURT: + attack_hitbox.set_active(false) + sprite.play(&"gate_warden_hurt") + sprite.frame = 0 + State.DEAD: + attack_hitbox.set_active(false) + sprite.play(&"gate_warden_death") + sprite.frame = 0 + + +func _exit_state(_previous: int) -> void: + attack_hitbox.set_active(false) + + +func _update_state(delta: float) -> void: + match _state: + State.IDLE: + _decelerate(delta) + if _target_in_range(config.detection_range): + change_state(State.CHASE) + State.CHASE: + _chase(delta) + State.WINDUP: + _decelerate(delta) + _face_target() + if sprite.frame >= ACTIVE_FIRST_FRAME: + change_state(State.ATTACK) + State.ATTACK: + _decelerate(delta) + if sprite.frame > ACTIVE_LAST_FRAME: + change_state(State.RECOVER) + State.RECOVER: + _decelerate(delta) + if not sprite.is_playing(): + _cooldown = config.attack_cooldown + change_state(State.IDLE) + State.HURT: + _decelerate(delta, 400.0) + if not sprite.is_playing(): + change_state(State.CHASE if _target_in_range(config.detection_range) else State.IDLE) + State.DEAD: + pass + + +func _sync_attack_window() -> void: + var active := ( + not _is_dead + and sprite.animation == &"gate_warden_attack_1" + and sprite.frame >= ACTIVE_FIRST_FRAME + and sprite.frame <= ACTIVE_LAST_FRAME + ) + attack_hitbox.set_active(active) + + +func _on_animation_finished() -> void: + if _state != State.DEAD or _boss_death_published: + return + _boss_death_published = true EventBus.boss_died.emit( - EventBus.context({"actor_id": actor_id(), "source_id": source_id}) + EventBus.context({"actor_id": actor_id(), "source_id": _death_source_id}) ) defeated.emit(self) diff --git a/data/actors/boss_gate_guardian.tres b/data/actors/boss_gate_guardian.tres index c068267..c7b809f 100644 --- a/data/actors/boss_gate_guardian.tres +++ b/data/actors/boss_gate_guardian.tres @@ -16,10 +16,10 @@ attack_range = 78.0 preferred_gap = 48.0 attack_damage = 22.0 attack_skill_multiplier = 1.0 -attack_windup = 0.55 -attack_active = 0.18 -attack_recovery = 0.75 +attack_windup = 0.445 +attack_active = 0.222 +attack_recovery = 0.445 attack_cooldown = 0.6 -hurt_duration = 0.14 -death_duration = 1.4 +hurt_duration = 0.5 +death_duration = 2.05 telegraph_tint = Color(1.9, 1, 1.4, 1) From 592c10a7412267b883e5a18669cc714e591aa76c Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:49 +1000 Subject: [PATCH 6/9] [M1.5][Codex] Replace combat effects and HUD icons --- scenes/main.tscn | 14 +++++++--- ui/debug_panel.gd | 17 +++++++++++- ui/hud.tscn | 53 +++++++++++++++++++++++++++++++------ ui/sacrifice_panel.tscn | 3 ++- visuals/combat_effects.gd | 54 ++++++++++++++++++++++++++++++++++++++ visuals/one_shot_sprite.gd | 20 ++++++++++++++ 6 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 visuals/combat_effects.gd create mode 100644 visuals/one_shot_sprite.gd diff --git a/scenes/main.tscn b/scenes/main.tscn index 6da8a80..2ed3ee2 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1,18 +1,20 @@ -[gd_scene load_steps=13 format=3 uid="uid://bnnmain0000001"] +[gd_scene load_steps=15 format=3 uid="uid://bnnmain0000001"] [ext_resource type="Script" path="res://scenes/main.gd" id="1_main"] [ext_resource type="PackedScene" path="res://scenes/arena.tscn" id="2_arena"] [ext_resource type="Script" path="res://systems/run_coordinator.gd" id="3_coordinator"] [ext_resource type="PackedScene" path="res://actors/player/player.tscn" id="4_player"] -[ext_resource type="PackedScene" path="res://actors/enemies/ghost_archer.tscn" id="5_enemy"] +[ext_resource type="PackedScene" path="res://actors/enemies/ghost_melee.tscn" id="5_enemy"] [ext_resource type="PackedScene" path="res://actors/boss/boss.tscn" id="6_boss"] -[ext_resource type="Resource" path="res://data/actors/ghost_archer.tres" id="7_enemy_config"] +[ext_resource type="Resource" path="res://data/actors/ghost_melee.tres" id="7_enemy_config"] [ext_resource type="Resource" path="res://data/actors/boss_gate_guardian.tres" id="8_boss_config"] [ext_resource type="PackedScene" path="res://ui/hud.tscn" id="9_hud"] [ext_resource type="PackedScene" path="res://ui/sacrifice_panel.tscn" id="10_sacrifice"] [ext_resource type="PackedScene" path="res://ui/result_screen.tscn" id="11_result"] [ext_resource type="PackedScene" path="res://ui/debug_panel.tscn" id="12_debug"] [ext_resource type="Script" path="res://ui/debug_shape_overlay.gd" id="13_shapes"] +[ext_resource type="Script" path="res://visuals/combat_effects.gd" id="14_effects"] +[ext_resource type="Texture2D" path="res://assets_v2/effects/hit.png" id="15_hit"] [node name="Main" type="Node2D"] script = ExtResource("1_main") @@ -21,6 +23,12 @@ script = ExtResource("1_main") [node name="Actors" type="Node2D" parent="."] +[node name="CombatEffects" type="Node2D" parent="."] +script = ExtResource("14_effects") +hit_texture = ExtResource("15_hit") +hit_frames = 4 +hit_fps = 16.0 + [node name="RunCoordinator" type="Node" parent="."] script = ExtResource("3_coordinator") player_scene = ExtResource("4_player") diff --git a/ui/debug_panel.gd b/ui/debug_panel.gd index 1490e9f..2eef6fa 100644 --- a/ui/debug_panel.gd +++ b/ui/debug_panel.gd @@ -12,6 +12,8 @@ extends CanvasLayer const HEAL_AMOUNT := 25.0 const DAMAGE_AMOUNT := 15.0 +const GHOST_ARCHER_SCENE := preload("res://actors/enemies/ghost_archer.tscn") +const GHOST_ARCHER_CONFIG := preload("res://data/actors/ghost_archer.tres") @onready var _panel: Panel = $Root/Panel @onready var _readout: Label = $Root/Panel/Readout @@ -43,7 +45,7 @@ func _unhandled_input(event: InputEvent) -> void: _coordinator.debug_damage(DAMAGE_AMOUNT) _note("damaged %.0f" % DAMAGE_AMOUNT) elif event.is_action_pressed(&"debug_spawn_enemy"): - _coordinator.debug_spawn_reference_enemy() + spawn_ghost_archer_for_debug() _note("spawned Ghost Archer") elif event.is_action_pressed(&"debug_start_boss"): _coordinator.begin_boss() @@ -110,3 +112,16 @@ func _describe(result: SacrificeResult, verb: String) -> String: func _note(message: String) -> void: _last_message = message print("[debug] ", message) + + +func spawn_ghost_archer_for_debug() -> void: + # Keep the existing coordinator-owned spawn/list/death path. Its scene and + # config are injected public composition properties, so the debug command can + # select X02 without reaching into private coordinator state. + var previous_scene := _coordinator.enemy_scene + var previous_config := _coordinator.enemy_config + _coordinator.enemy_scene = GHOST_ARCHER_SCENE + _coordinator.enemy_config = GHOST_ARCHER_CONFIG + _coordinator.debug_spawn_reference_enemy() + _coordinator.enemy_scene = previous_scene + _coordinator.enemy_config = previous_config diff --git a/ui/hud.tscn b/ui/hud.tscn index c4b68a0..37906a5 100644 --- a/ui/hud.tscn +++ b/ui/hud.tscn @@ -1,10 +1,13 @@ -[gd_scene load_steps=6 format=3 uid="uid://bnnhud0000001"] +[gd_scene load_steps=9 format=3 uid="uid://bnnhud0000001"] [ext_resource type="Script" path="res://ui/hud.gd" id="1_hud"] [ext_resource type="Texture2D" path="res://assets/ui/ui_health_bar.png" id="2_health_bar"] [ext_resource type="Texture2D" path="res://assets/ui/ui_stamina_bar.png" id="3_stamina_bar"] -[ext_resource type="Texture2D" path="res://assets/icons/ui/icon_hp.png" id="4_icon_hp"] -[ext_resource type="Texture2D" path="res://assets/icons/ui/icon_boss.png" id="5_icon_boss"] +[ext_resource type="Texture2D" path="res://assets_v2/icons/icon_yang.png" id="4_icon_hp"] +[ext_resource type="Texture2D" path="res://assets_v2/icons/icon_obsession.png" id="5_icon_boss"] +[ext_resource type="Texture2D" path="res://assets_v2/icons/icon_qi.png" id="6_icon_qi"] +[ext_resource type="Texture2D" path="res://assets_v2/icons/icon_songdao.png" id="7_icon_attack"] +[ext_resource type="Texture2D" path="res://assets_v2/icons/icon_ghostfire.png" id="8_icon_imbalance"] [node name="Hud" type="CanvasLayer"] script = ExtResource("1_hud") @@ -14,11 +17,20 @@ offset_right = 240.0 offset_bottom = 120.0 [node name="Icon" type="TextureRect" parent="Player"] -offset_left = 4.0 -offset_top = 4.0 -offset_right = 36.0 -offset_bottom = 36.0 +offset_left = 6.0 +offset_top = 6.0 +offset_right = 22.0 +offset_bottom = 22.0 texture = ExtResource("4_icon_hp") +expand_mode = 1 + +[node name="StaminaIcon" type="TextureRect" parent="Player"] +offset_left = 6.0 +offset_top = 24.0 +offset_right = 22.0 +offset_bottom = 40.0 +texture = ExtResource("6_icon_qi") +expand_mode = 1 [node name="HealthBar" type="TextureRect" parent="Player"] offset_left = 40.0 @@ -49,7 +61,7 @@ offset_bottom = 15.0 color = Color(0.165, 0.541, 0.29, 1) [node name="Stats" type="Label" parent="Player"] -offset_left = 6.0 +offset_left = 24.0 offset_top = 44.0 offset_right = 236.0 offset_bottom = 96.0 @@ -59,6 +71,30 @@ text = "ATK EHP Integrity" +[node name="AttackIcon" type="TextureRect" parent="Player"] +offset_left = 6.0 +offset_top = 45.0 +offset_right = 20.0 +offset_bottom = 59.0 +texture = ExtResource("7_icon_attack") +expand_mode = 1 + +[node name="IntegrityIcon" type="TextureRect" parent="Player"] +offset_left = 6.0 +offset_top = 61.0 +offset_right = 20.0 +offset_bottom = 75.0 +texture = ExtResource("5_icon_boss") +expand_mode = 1 + +[node name="ImbalanceIcon" type="TextureRect" parent="Player"] +offset_left = 6.0 +offset_top = 77.0 +offset_right = 20.0 +offset_bottom = 91.0 +texture = ExtResource("8_icon_imbalance") +expand_mode = 1 + [node name="Boss" type="Control" parent="."] offset_left = 220.0 offset_right = 480.0 @@ -70,6 +106,7 @@ offset_top = 4.0 offset_right = 36.0 offset_bottom = 36.0 texture = ExtResource("5_icon_boss") +expand_mode = 1 [node name="BossBar" type="TextureRect" parent="Boss"] offset_left = 40.0 diff --git a/ui/sacrifice_panel.tscn b/ui/sacrifice_panel.tscn index 71c551d..1a45ce0 100644 --- a/ui/sacrifice_panel.tscn +++ b/ui/sacrifice_panel.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://bnnsacpanel001"] [ext_resource type="Script" path="res://ui/sacrifice_panel.gd" id="1_panel"] -[ext_resource type="Texture2D" path="res://assets/icons/ui/icon_sacrifice.png" id="2_icon"] +[ext_resource type="Texture2D" path="res://assets_v2/icons/icon_talisman.png" id="2_icon"] [node name="SacrificePanel" type="CanvasLayer"] layer = 5 @@ -28,6 +28,7 @@ offset_top = 8.0 offset_right = 40.0 offset_bottom = 40.0 texture = ExtResource("2_icon") +expand_mode = 1 [node name="Title" type="Label" parent="Root/Panel"] offset_left = 46.0 diff --git a/visuals/combat_effects.gd b/visuals/combat_effects.gd new file mode 100644 index 0000000..8acd2fc --- /dev/null +++ b/visuals/combat_effects.gd @@ -0,0 +1,54 @@ +class_name CombatEffects +extends Node2D +## EventBus observer for confirmed damage feedback. No calculation or gameplay +## mutation occurs here; the existing CombatResolver result is already final. + +@export var hit_texture: Texture2D +@export var hit_frames: int = 4 +@export var hit_fps: float = 16.0 + + +func _ready() -> void: + z_index = 20 + EventBus.hit_dealt.connect(_on_hit) + EventBus.hit_taken.connect(_on_hit) + + +func _on_hit(payload: Dictionary) -> void: + if float(payload.get("final_damage", 0.0)) <= 0.0 or hit_texture == null: + return + var target := _find_actor(StringName(payload.get("target_id", &"unknown"))) + if target == null: + return + var effect := OneShotSprite.new() + effect.configure(hit_texture, hit_frames, hit_fps) + add_child(effect) + effect.global_position = target.global_position + Vector2(0.0, -32.0) + + +func _find_actor(target_id: StringName) -> Node2D: + var candidates: Array[Node2D] = [] + _collect_actors(get_tree().current_scene, target_id, candidates) + if candidates.is_empty(): + return null + if candidates.size() == 1: + return candidates[0] + var players: Array[Node2D] = [] + _collect_actors(get_tree().current_scene, &"player", players) + var player: Node2D = players[0] if not players.is_empty() else null + if player == null: + return candidates[0] + var closest := candidates[0] + for candidate in candidates: + if candidate.global_position.distance_squared_to(player.global_position) < closest.global_position.distance_squared_to(player.global_position): + closest = candidate + return closest + + +func _collect_actors(node: Node, target_id: StringName, output: Array[Node2D]) -> void: + if node == null: + return + if node is Node2D and node.has_method("actor_id") and node.call("actor_id") == target_id: + output.append(node as Node2D) + for child in node.get_children(): + _collect_actors(child, target_id, output) diff --git a/visuals/one_shot_sprite.gd b/visuals/one_shot_sprite.gd new file mode 100644 index 0000000..e1db9d6 --- /dev/null +++ b/visuals/one_shot_sprite.gd @@ -0,0 +1,20 @@ +class_name OneShotSprite +extends Sprite2D +## Small self-cleaning frame-strip player for combat feedback only. + +var _fps: float = 1.0 +var _elapsed: float = 0.0 + + +func configure(source: Texture2D, frame_count: int, fps: float) -> void: + texture = source + hframes = frame_count + _fps = fps + frame = 0 + + +func _process(delta: float) -> void: + _elapsed += delta + frame = mini(int(_elapsed * _fps), hframes - 1) + if _elapsed >= float(hframes) / _fps: + queue_free() From 0ad2374991d864857fa1a42887ca1a3053fa538d Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:54 +1000 Subject: [PATCH 7/9] [M1.5][Codex] Add animation integration tests --- tests/cases/test_actors.gd | 2 + tests/cases/test_art_v2.gd | 182 +++++++++++++++++++++++++++++++ tests/cases/test_ghost_archer.gd | 19 +++- tests/cases/test_run_loop.gd | 15 ++- 4 files changed, 209 insertions(+), 9 deletions(-) create mode 100644 tests/cases/test_art_v2.gd diff --git a/tests/cases/test_actors.gd b/tests/cases/test_actors.gd index d40227f..7730d01 100644 --- a/tests/cases/test_actors.gd +++ b/tests/cases/test_actors.gd @@ -105,6 +105,8 @@ func test_boss_death_publishes_its_own_events_once() -> void: boss.receive_damage(_blow(boss, 9999.0)) boss.die(&"test") + assert_equal(defeated[0], 0, "victory waits while the collapse animation plays") + await step_physics(125) EventBus.boss_died.disconnect(handler) assert_equal(defeated[0], 1, "defeated fired once") diff --git a/tests/cases/test_art_v2.gd b/tests/cases/test_art_v2.gd new file mode 100644 index 0000000..1114604 --- /dev/null +++ b/tests/cases/test_art_v2.gd @@ -0,0 +1,182 @@ +extends TestCase +## M1.5 Art V2 resource, state mapping and authored-frame timing coverage. + +const MAIN_SCENE := "res://scenes/main.tscn" +const PLAYER_SCENE := "res://actors/player/player.tscn" +const MELEE_SCENE := "res://actors/enemies/ghost_melee.tscn" +const ARCHER_SCENE := "res://actors/enemies/ghost_archer.tscn" +const ARROW_SCENE := "res://actors/enemies/ghost_arrow.tscn" +const BOSS_SCENE := "res://actors/boss/boss.tscn" + +var _main: Main +var _spawned: Array[Node] = [] + + +func after_each() -> void: + Input.action_release(&"move_left") + Input.action_release(&"move_right") + Input.action_release(&"jump") + Input.action_release(&"light_attack") + if _main != null and is_instance_valid(_main): + _main.queue_free() + for node in _spawned: + if is_instance_valid(node): + node.queue_free() + _main = null + _spawned.clear() + + +func _launch() -> RunCoordinator: + _main = (load(MAIN_SCENE) as PackedScene).instantiate() + tree.root.add_child(_main) + await step_physics(3) + return _main.coordinator() + + +func _spawn_enemy(scene_path: String, config_path: String, target: Node2D) -> EnemyBase: + var enemy := (load(scene_path) as PackedScene).instantiate() as EnemyBase + tree.root.add_child(enemy) + _spawned.append(enemy) + var enemy_config := (load(config_path) as EnemyConfig).duplicate(true) as EnemyConfig + enemy_config.gravity = 0.0 + enemy.initialize(enemy_config, target) + return enemy + + +func test_all_integrated_v2_resources_load_with_declared_animations() -> void: + var player_frames := load("res://assets_v2/godot/player_frames.tres") as SpriteFrames + var melee_frames := load("res://assets_v2/godot/melee_ghost_frames.tres") as SpriteFrames + var archer_frames := load("res://assets_v2/godot/ghost_archer_frames.tres") as SpriteFrames + var boss_frames := load("res://assets_v2/godot/gate_warden_frames.tres") as SpriteFrames + assert_not_null(player_frames, "player SpriteFrames load") + assert_not_null(melee_frames, "melee ghost SpriteFrames load") + assert_not_null(archer_frames, "Ghost Archer SpriteFrames load") + assert_not_null(boss_frames, "Gate Warden SpriteFrames load") + assert_equal(player_frames.get_frame_count(&"player_light_attack_1"), 8, "player attack has 8 frames") + assert_equal(melee_frames.get_frame_count(&"melee_ghost_attack"), 8, "melee attack has 8 frames") + assert_equal(archer_frames.get_frame_count(&"ghost_archer_shoot"), 4, "archer shoot has 4 frames") + assert_equal(boss_frames.get_frame_count(&"gate_warden_attack_1"), 10, "Boss attack has 10 frames") + assert_not_null(load("res://assets_v2/projectiles/ghost_fire_arrow.png"), "Ghost Arrow texture imports") + assert_not_null(load("res://assets_v2/effects/hit.png"), "hit effect imports") + assert_not_null(load("res://assets_v2/effects/blade_slash.png"), "slash effect imports") + + +func test_actor_scenes_use_v2_frames_and_stable_foot_offsets() -> void: + var player := (load(PLAYER_SCENE) as PackedScene).instantiate() as Player + var melee := (load(MELEE_SCENE) as PackedScene).instantiate() as MeleeGhost + var archer := (load(ARCHER_SCENE) as PackedScene).instantiate() as GhostArcher + var boss := (load(BOSS_SCENE) as PackedScene).instantiate() as BossActor + for actor in [player, melee, archer, boss]: + tree.root.add_child(actor) + _spawned.append(actor) + assert_equal(player.sprite.offset, Vector2(-48, -88), "player feet use V2 baseline") + assert_equal(melee.sprite.offset, Vector2(-48, -90), "melee feet use V2 baseline") + assert_equal(archer.sprite.offset, Vector2(-48, -88), "archer feet use V2 baseline") + assert_equal(boss.sprite.offset, Vector2(-96, -176), "Boss feet use V2 baseline") + var arrow := (load(ARROW_SCENE) as PackedScene).instantiate() as GhostArrow + tree.root.add_child(arrow) + _spawned.append(arrow) + assert_true(arrow.uses_v2_texture(), "normal projectile rendering uses the supplied V2 texture") + assert_false(arrow.get_node("FallbackVisual").visible, "procedural fallback is hidden") + + +func test_player_maps_idle_run_jump_fall_and_attack() -> void: + var coordinator := await _launch() + var player := coordinator.player() + assert_equal(player.sprite.animation, &"player_idle", "grounded rest selects idle") + + Input.action_press(&"move_right") + await step_physics(3) + assert_equal(player.sprite.animation, &"player_run", "movement selects run") + assert_false(player.sprite.flip_h, "right-facing run is not flipped") + Input.action_release(&"move_right") + Input.action_press(&"move_left") + await step_physics(2) + assert_true(player.sprite.flip_h, "left-facing run flips the sprite") + Input.action_release(&"move_left") + + Input.action_press(&"jump") + await step_physics(2) + Input.action_release(&"jump") + assert_equal(player.sprite.animation, &"player_jump", "rising selects jump") + player.velocity.y = 40.0 + await step_physics(2) + assert_equal(player.sprite.animation, &"player_fall", "descending selects fall") + + Input.action_press(&"light_attack") + await step_physics(2) + Input.action_release(&"light_attack") + assert_equal(player.sprite.animation, &"player_light_attack_1", "current light action selects attack 1") + + +func test_player_hitbox_matches_only_authored_active_frames() -> void: + var coordinator := await _launch() + var player := coordinator.player() + Input.action_press(&"light_attack") + await step_physics(2) + Input.action_release(&"light_attack") + var saw_startup := false + var saw_active := false + var saw_recovery := false + for _frame in range(45): + await step_physics(1) + if player.sprite.animation != &"player_light_attack_1": + continue + var authored_active := player.sprite.frame >= 4 and player.sprite.frame <= 5 + assert_equal(player.attack_hitbox.is_active(), authored_active, "hitbox follows attack frame %d" % player.sprite.frame) + saw_startup = saw_startup or player.sprite.frame < 4 + saw_active = saw_active or authored_active + saw_recovery = saw_recovery or player.sprite.frame > 5 + assert_true(saw_startup, "startup frames were observed") + assert_true(saw_active, "active frames were observed") + assert_true(saw_recovery, "recovery frames were observed") + assert_false(player.attack_hitbox.is_active(), "hitbox closes after the animation") + + +func test_player_hurt_and_death_override_locomotion() -> void: + var coordinator := await _launch() + var player := coordinator.player() + coordinator.debug_damage(20.0) + await step_physics(1) + assert_equal(player.sprite.animation, &"player_hurt", "hurt overrides locomotion") + await step_physics(35) + coordinator.debug_damage(10000.0) + await step_physics(1) + assert_equal(player.sprite.animation, &"player_death", "lethal damage selects death") + + +func test_melee_and_boss_hitboxes_follow_authored_frames() -> void: + var target := Node2D.new() + tree.root.add_child(target) + _spawned.append(target) + target.global_position = Vector2(40.0, 0.0) + var melee := _spawn_enemy(MELEE_SCENE, "res://data/actors/ghost_melee.tres", target) + var saw_melee_active := false + for _i in range(60): + await step_physics(1) + if melee.sprite.animation == &"melee_ghost_attack": + var expected := melee.sprite.frame >= 4 and melee.sprite.frame <= 5 + assert_equal(melee.attack_hitbox.is_active(), expected, "melee hitbox follows frame map") + saw_melee_active = saw_melee_active or expected + assert_true(saw_melee_active, "melee active frames occurred") + + target.global_position = Vector2(60.0, 0.0) + var boss := _spawn_enemy(BOSS_SCENE, "res://data/actors/boss_gate_guardian.tres", target) + var saw_boss_active := false + for _i in range(70): + await step_physics(1) + if boss.sprite.animation == &"gate_warden_attack_1": + var expected := boss.sprite.frame >= 4 and boss.sprite.frame <= 5 + assert_equal(boss.attack_hitbox.is_active(), expected, "Boss hitbox follows frame map") + saw_boss_active = saw_boss_active or expected + assert_true(saw_boss_active, "Boss active frames occurred") + + +func test_debug_collision_overlay_is_off_by_default_and_still_toggles() -> void: + await _launch() + var overlay := _main.get_node("DebugRoot/DebugShapes") as DebugShapeOverlay + assert_false(overlay.is_enabled(), "debug shapes start disabled") + assert_false(overlay.visible, "no collision geometry appears in normal play") + overlay.toggle() + assert_true(overlay.is_enabled(), "existing debug toggle still enables shapes") + assert_true(overlay.visible, "enabled overlay becomes visible") diff --git a/tests/cases/test_ghost_archer.gd b/tests/cases/test_ghost_archer.gd index 73b5cb3..0699b2a 100644 --- a/tests/cases/test_ghost_archer.gd +++ b/tests/cases/test_ghost_archer.gd @@ -120,12 +120,21 @@ func test_archer_aims_shoots_and_respects_cooldown() -> void: var target := _target(Vector2(150.0, 0.0)) var archer := _spawn_archer(target) var shots := [0] - archer.arrow_fired.connect(func(_arrow: GhostArrow) -> void: shots[0] += 1) - - await step_physics(3) - assert_equal(archer.state(), EnemyBase.State.WINDUP, "preferred range begins the readable aim") - await step_physics(36) + var release_frames := [] + archer.arrow_fired.connect(func(_arrow: GhostArrow) -> void: + shots[0] += 1 + release_frames.append(archer.sprite.frame) + ) + + var saw_aim := false + for _i in range(20): + await step_physics(1) + saw_aim = saw_aim or archer.state() == EnemyBase.State.WINDUP + assert_true(saw_aim, "preferred range begins the readable aim") + await step_physics(45) assert_equal(shots[0], 1, "one arrow is released after the wind-up") + if not release_frames.is_empty(): + assert_equal(release_frames[0], 1, "the arrow releases on authored shoot frame 1") await step_physics(55) assert_equal(shots[0], 1, "recovery and cooldown prevent an immediate second shot") diff --git a/tests/cases/test_run_loop.gd b/tests/cases/test_run_loop.gd index e73b04b..a7de0ed 100644 --- a/tests/cases/test_run_loop.gd +++ b/tests/cases/test_run_loop.gd @@ -110,7 +110,7 @@ func test_full_loop_reaches_victory_and_restarts() -> void: assert_greater(state.imbalance(), float(before["imbalance"]), "imbalance rose") coordinator.boss().die(&"test") - await step_physics(2) + await step_physics(125) assert_equal(coordinator.phase(), RunCoordinator.PHASE_RESULT, "the run ends") assert_equal(coordinator.state().run_status(), RunState.STATUS_VICTORY, "as a victory") @@ -160,12 +160,19 @@ func test_debug_commands_route_through_the_coordinator() -> void: var enemies_before := coordinator.live_enemies().size() coordinator.debug_spawn_reference_enemy() assert_equal( - coordinator.live_enemies().size(), enemies_before + 1, "debug spawn added an enemy" + coordinator.live_enemies().size(), enemies_before + 1, "reference spawn added the wave enemy" ) assert_true( - coordinator.live_enemies()[-1] is GhostArcher, - "the injected debug enemy is the Ghost Archer" + coordinator.live_enemies()[-1] is MeleeGhost, + "the coordinator reference spawn uses the injected wave composition" + ) + + var debug_panel: DebugPanel = _main.get_node("DebugRoot/DebugPanel") + debug_panel.spawn_ghost_archer_for_debug() + assert_equal( + coordinator.live_enemies().size(), enemies_before + 2, "F4 debug spawn added an enemy" ) + assert_true(coordinator.live_enemies()[-1] is GhostArcher, "F4 spawns the Ghost Archer") var digest := state.snapshot_hash() var preview := coordinator.debug_preview_sacrifice() From 5142f8a54d4c301fe4125aa1e33bfd7bd1035bfa Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:43:59 +1000 Subject: [PATCH 8/9] [Docs][Codex] Document Art V2 integration handoff --- README.md | 5 +- assets_v2/review/integration_archer_aim.png | Bin 0 -> 23665 bytes assets_v2/review/integration_archer_arrow.png | Bin 0 -> 23947 bytes assets_v2/review/integration_boss_attack.png | Bin 0 -> 29254 bytes assets_v2/review/integration_boss_death.png | Bin 0 -> 29190 bytes assets_v2/review/integration_idle.png | Bin 0 -> 22377 bytes assets_v2/review/integration_jump.png | Bin 0 -> 22620 bytes .../integration_player_attack_hitbox.png | Bin 0 -> 23489 bytes assets_v2/review/integration_run.png | Bin 0 -> 22342 bytes docs/AI_HANDOFF.md | 238 +++++++----------- docs/ART_V2_INTEGRATION_REPORT.md | 105 ++++++++ docs/DECISIONS.md | 22 ++ docs/TASKS/README.md | 19 +- docs/TESTING.md | 13 +- 14 files changed, 244 insertions(+), 158 deletions(-) create mode 100644 assets_v2/review/integration_archer_aim.png create mode 100644 assets_v2/review/integration_archer_arrow.png create mode 100644 assets_v2/review/integration_boss_attack.png create mode 100644 assets_v2/review/integration_boss_death.png create mode 100644 assets_v2/review/integration_idle.png create mode 100644 assets_v2/review/integration_jump.png create mode 100644 assets_v2/review/integration_player_attack_hitbox.png create mode 100644 assets_v2/review/integration_run.png create mode 100644 docs/ART_V2_INTEGRATION_REPORT.md diff --git a/README.md b/README.md index 20cfe2d..54928c1 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ Product first, then implementation: 5. [AI handoff](docs/AI_HANDOFF.md) — current state, known issues, next tasks 6. [Contributor rules](AGENTS.md) and [GitHub workflow](docs/GITHUB_WORKFLOW.md) -Art assets are governed by [docs/ART_SPEC.md](docs/ART_SPEC.md). All sprites are placeholders, marked with a magenta border. +The playable actors, projectile, combat effects, and matching HUD fields use the +production Art V2 pack. The legacy placeholder set remains as source material +for modules not yet integrated; see +[docs/ART_V2_INTEGRATION_REPORT.md](docs/ART_V2_INTEGRATION_REPORT.md). This repository is a greybox prototype, not a finished or publicly supported game. diff --git a/assets_v2/review/integration_archer_aim.png b/assets_v2/review/integration_archer_aim.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0afdc2389a94e7f4254b9b07f71d181704802a GIT binary patch literal 23665 zcmc$`2UJt<*Cu)bBE3uRpaM!2rIR3_phyuFkX}?!dM|+ll`2h85u^kJ!9wW03(};7 z-h1yolt7X>{QlqnesjN>S!>q1YvyLL2q%Y}cW1x5Jp0-EBtl z06-E101$hMi{LA*H|{?KpCBFvnrc8^H}@(4@B{ZWZa?--*_iYTxU`5T-_eszenweI zNvY6A795nq$jHFJ$haedy;jQlWA16qNvYuvldPd@PfF4*nbN=N6@2m&3n-|mE>JM= zt5Q@-HZIY%$J_3o+9q}J7FQP=9VTK1RFfg8Ei1xo2 zGqc-HOF?`O4*!lEts;n?{-6{5iV!k8&I(q74_y)5^ESwT32}s67DUcINVH|RENJ;+ z`__ZmZkzCMTyiEP=67s(K&r~H^hH&{z3T=o_)2#rK|vR5j8~L-gT{i7{Kfa-3x8c& zfVwp9xFp z=sHIu*hnsT`SJxXFDwu=FrZOVnyTsVf5Y8-Gt6*2vaj!2R`%6Z(wR?2<`IX75(`Tk zs%jI;+#{C3b8}QcLkM;1i`LJ|v=1yB`USUJZZ1qFW}kkUjSa7%SO%4Csc#CW+ubb$ zg8uxG|M>ASzrbxd925#=edQ%oRRsrtrqFLQ2dg*CDtaQa{`dl_FJE2>rPB!#JLI;p`I`RtqM(4aRCkJKnBlKGo{#0{=E-L_Mm>Na{^$JQ8mf2h&|bUtO3E=& z;%%ymX>3{=FAWD%PfU`LY%;H};_$(*y|^Gs+7;t6%-#SlsPXx^ zi$tlNoQ8(;td@^NH6o&od92$fy|$Kg*X=C>A73PTni#aUrg-^ki;#o_YZyHn02n;_ z&@(s~;<3yrWfeqKY^Qfyw;N#(hd(qlj2;-cOv9e8W@}6B?(Nw6@udu-qAUO5rqY}E zzH5N$QFDTZy7%-2gAb-#3$YJ07A&%F>;J6+_ZFSK#CnURSps;}=pP~|Yz^T$t%^Gi(6 zjsR<{lM&E4W#%|Ul~`NJ9`Kc$(N738=)D)Ec9_|{_IbCp$CN;NRTbT%oCggj`ZUWm)@$9nB|bUDgds0?V7{GCtj?iqf>B;!{#S7~Wg z!RPq+@Vq>U6bZ3G@(O0*ZmX84dn2XmfPaa}i08nZmgnrO(fS z@d?TZM1*I#UhYhS$|c@@A0EJX?n6)KmCw(gi|{OR|9sU^51!&`>f_%d6q(z^#|CfO zc5WYTg@(5+a<6tjAC_c1A%*87y~!HLZG{RBS`>gFujFz;C0VL^Z9ShXT|vQe=~{;S zPd|sZ@oCi(Mj3(9yVK8G?Og$6alc4bdSCepn1nk(%Fq;qne!a;o4DIq3RwVNyv=dF(>#^o2e_!xx%Z4XpLSmy0^b!L06Eu3rF)ZdP+I1b3-GE7f_u0B5f@Y}gwa~M7XO%<@nOBqe+jx; zd07_yBP3cHh^8c|RxlLSb)rGZ82yy~x%hK1$V1+TpS{c6Kl%6its`JuM}&b9TPyE8 z_CJgDfiQ!buOeVg0kax=3D(IQT-sYmMz6K2ooNb;9ejkOQGaS@KPHzv#08X7_wTY4 zbuBK=*w02PK{+VPu*R?VGre5QeXk(AS_96`jIQ@ijL`wdC*s9!`+9kQPWE|>@i<|5 z6RWecx!_}VW$Yix_cw0xRv#|R56TgvdC}NM46HvJbNHa-Kt<1JNaI(kGh4iz{8M8( zz}EvSq@|~=YG=o$i~8}$!37!m_tmYUyEM(}N7KPs!NvmvvNNSV(0&)8L@94}!?PFC zLp7C*mGD>+;HsUS^=KgM&PotNF7X*Pl=!N=S2n5X!|qI9h?XADoA@G=nvx~U?|JC* zT{Gp-(t-<0aliMjK9P~ibeku{l6O?@uqOvWR28AZ~LE%WeGzQAV3WXMk|Wh~NX zripg?OGrKWRvn}5-EUmt9a=gKXllFJa!gI+L!n2zJ%MtwQ10Q)cQhLi2)K80a(j;#6>{`>whe2d>L})yv*BK z>;7fXtIr_RYG+2I=5*>sXD#lABW_#5)9Wa`vXp0&hx330$EvIztL!RszvI>JbUsoF zjxP94k}SL32%93H5u?Y2O|Banl)7w286`EW+S@gBl)KX36~=qYyA!Q_{TJci(GD8j zTSAE`saF($G9R;`syYrn=VLnCyE(Q~DecFnxO@n}CgFM8d^#$%Qqy(XTn+%?K^*-pg2WY$4Hf;Mu&sK^x8!%`%mRxCo$|oX zY!#>Y{uOVo3Bm1BUuij>uX!L9ZZ#Mz6u>c^jWxrd-OjcQbSt| zqj*2PC`L%<=zg}f@3YE$e*3TLgG$Fs*M6=60P*Ao+qwC4&bAv9+gZPJnM4i20uG`^ z5w_q}CnhdB<>#4J6&A9*wGq^Cpm=;?=4TRfK{G!$Q+tw-b@vqccVU7C`+g~+KUxlc*g82g%en$TUky}P*Ok};s(P_QE2lw0z=ey4_;3}I{$cBb zDisn^Qn!p^Z6f~Madh}#vAJgu5S@k(;PFgo>D-YHNg z?(UC|=k<^$!T`U5n^YS7RYaEaOeWctHvFszuQ87K3^6$E#JfI|p?0d2iNYH|SxkB(4%ZjeExn#hVoYeIgUP#SS}X+I)hZK~x!}kH0GoQ?#hYD`{4FjAFGl2n|!~ zD07HD1yT=+OJ8t$@qjB*vi{^YvfRN5H4vDK`Ssy;sS?nwD}+hv>Oeo+=H zTOx>s*U1@Flr5S>9Nm*!*fvh-OZ)ki++48fh&$z-cYS?DEc^)wyf-znjwIE5G9Jk) zh>Q5lK@CY~-WFEvdX^?V1cG$n zf5p-FH5R><{`M;k`2yTGZA@M{-o^vP_y#!(R9Yi8pP#_PK4 zxXol$qpZ`ilIu>7XbqwJs;9kT^dXO&JQXItf?6{s?YHTAU5_N{e; zy(&(JCD%Wr1M!q#E*(C$wuDTX;VJt1nR-S?L;CHm?T(G3GKx%`N@BsX0I|O!cv+aP z1s_{WrFxVyHw``Xc<@D@>B{p`V{D)1cNfRoY5_E9{&Q?v9GtP{@V(u&Wa1EPS?-Dg zouo3Ho^|?^N$5EW}@iFI3*|PNE zm<=~#WQh$r}(Nw1Xk2mIJdVYF@l`i5?-VKFIp@4Kh$62@-%dTb(H zOVUT|{XDd}5JwXxB^=zsx1Lj|sl6%iI=!7DBBp8mknE}sw`e7ugM``28!!_pdBj@T z8>Eann3nT3)XO@{m_QMoQVScofyk|S#2?w83Gt<9}X1qCgiZDaKesMC8EPlvt?`bx=?=;?M` zIJ`9WWJJkpI&@h{gM4F9G6`K+>^JyBd zKpBZ!l0wU(6b5?)Afa3;DrBQtxLVx$=*Yd+e$}l^scP$r;BKgH3dgjotTJ^S5hb z{(x%O(wk9PJs6Sq+O>7pI&<5@FDjgzCJY=Lp{jSlvadEgf=-hly`P{aJ*lFC;|$rR z4cx)F6bc?6Y%n$7dHk9ff#W|g0AAvEZEow?=YMdQ(Hd!htgLOQxkz4)C4D?Br^G`5 z(sSp;#>Nz-mVe$8$(N6eOHv0z1y|~nBv4Y4R(|~q((qb6$F=F~N84y>te?l#`JG-t z{2us6=8sBRF#d<>ST0zB04O+QwF+ro3i^-Te;>yuNbD8yk+bfiNAWe-znK6o`}L|` zZO=7#f|^EAGwXRV{J9jYh)$?t&MU0B z;-CmE?-WW8nl~B?9UHZwZn_L0E!N$6o(DR>+D#hNl%<;|6!s6}(w(;czeoch5BBep z7|xkEQ+*WzC=wA3i_I19z;M<3^ko~&Vq?`HyzTA3lF_ao(4_X{_@HUI;J6N!q(VK? zlfFXnEKAr5IVGAC%ONMvsi*A8l`ugR^oMj-<8B1(;}J?_v5ouJw;JOz?RJK$Bs(oo zDXAgPkMNf=NY&0YaeVe`lEUEPTzM>>`@_{3jeAeU4Qme=*SJjIGw{95NjoMp2yL+) zP7A2PE!FpCmQJl)!=`XlSrZzr3zi2AkdihZ_y24jDqYT&{|l`AXaXu~An@D3es)%I1r&l(8(za>eD zV1zr|YLDYy3HAzc0?X&P+F@9j31I7j3FY*&Cr?*4yW&?}lCgVSugRJ*^Oz&=ivr`P zC(8vOFyvRf#;v`T82jGp%95FvC<1@XNfQ&@SOiDD3lG=dP;zC;uf=nK)MbPipzyQ*Ce%}AN%$OROnSqlpv`mPQlXG=fxHH!NvN7^BMghT%{TY)He4v$2< zg?~1S1&c+pPlK<_5>%MLBAUaw-hle#gnn;nT@0{2)Ad@*6lj|Y6G zzDL>Zy-NQ0#w0(01Eg3!S3c)$*rL2g(WTq)_mlRntD&ro3<#X-r1kDrF;^2iBM zU}#+GP79W~GB{GS3Div$(aYiII0ocOX3niYzh+ky;fJBtx*yg2slQc7RDg9DgL-EP z+vP+M&!&69MB~MWAOk88Q;)f!{uhw}Cqno_kf<%#dYiBR@M%~wVwXmi=t932%VxW_ zpbY>YBzZ#k4p~;3_<-;0$-DEvKd`RHf`Qq?gR7*r>aeV(#gOJ*gz9;~h zq^}s%)SuWpG9{D?UcK6w;o)6Wz)i1wcCt1{wTAE)xkhKZO-5mRk#ZK8^YAlQ(A zv}tKK)Y22lPWN?fdb_F&yUzImuVv1{<21INWg}>4b}{wQbf6F&w_*rN{Aoo0olh0> zWeP8rb>!*ICspj795f%4;FsUDvHqJV>fB;&7}i^wofT_sM9qwEA7%ckCKuK-0{vxT z1Do~7SVJ>pzP%L*0VpU91)&n0C#{i;m$Edpv=|y2FS0v5GPH=)Ax@I14!?SFPj`#a zOa{(Gz@O+osJlsx3_OB)4^H)G-g?Ruq#8nE@|J;vfnQ8!>mw}%Ktjrs3_m^PA@7~c zyyF$kSqbC5<6uf2gA!{REXGYmewFzof|jQS&g;I}rg(i5i?<=en1&r1^Ur}`0t2gM z_D%Rgct*u$PI3CEW(BE&2WdEYEA zBL!NbbTyZDGAg#le&}Cg5fT4&OEGlSkD7wQ_1KKzso>ePuULwP z(g5GG!ec8VAGv>6esD`DC@7>%g+;U|L68sU^?zEQH+!Qy?ioHZGzS|$rP2U`2(1N{e5!aT@a97&IW>A5JP3p?dDAVNl(pC30$O1-SQpM21_aAOsc37ab| zm;0*utC@B`N6^j(y3~~v)ub5-03bWkSflWvcVHqk2?S`_k`qU*Fa8ufqtVmT2oeO6 zghFjLPc3cszGdo9_e4>rPF8r@WRJ>Hg^&pRv;tQWdd%{sSLo40qhWije9pUTQpl~v z?>`NN1gsf!Q^cAI-!M(cBRQ!euQ4d9hJaGbKQhUjUizO=1+^U*NSweweY4MH0B5MG zQ78-;N${XB(&dY#ABKj6CDez)>dqK>jjI^y&2F+S59k=5EYtUbDAr@`>KS%146@QP zhW4Gp-MmUB+@Wt^@|J$;glDLxlyTgLr=@#Rv*2tNHTywl{K~N0=ALvL%rNTBqvyx# z)yX^mZ$R}BWY0i|@Mo+Z`ns>ka99_${sn%3X$P75fp*eGH-urEEQ4RXzPuRnhB%WcOsqienf zTQ0eCTx7aSP}2KamCaj^u%RX3y8i*vj>oMs_AbL#pz1lv8bBNzQ1w#D;;gCGUTOOE zEf?p3*v!lBZ0C&w0$9c3X_0 zFhSV7R-(s33nSPCwcWnend97ahC%vRcdpO`>~w$d-5=C++w)YYn-;*o_y~5?p!k`m zf9T%*A?HKRx7?3tdz6v{HZ0mcgT0FVA7Fb#&>`hzvn@!Yp4@=vThj#nyQg}8?N;`9ZyyDb6Vag;?-xc(~RJcCY~u9{w!!|M=m@h+S*lO@0Ac zrdw+-!pifZ<4Xg({|E&=?;i^a#&i4!rnLr3t^cow-*Tu|wzdvEDK)@HQ6mV&!AH`6 zx$?uSf9@*QGTR+Kv>04C_(vEm#plsgqia-@g>eO?F-qma=kFV~VUs$iSvsXaEYHZ; zPD9H7f9No(3oy=fF2nh&Ic7Z?X9ib!NIY-5+OVYq)z<5V^4QqI)rd~6%P)BlA-f#9pHjS z_rkfvs%o;g%wR@p8X60ej-VmAykN(D88>mtYm(B^(D(yrLB#}|3z~cE+FVnED}K|w z20sX#CTPJUm{mn|7W3G{^cMnt@iu68*DtE>i5i&Va~aF*9KelkX90Z(HT!PIuC3q} z=rmfo>_EG4h2j|{J4r;iRt$KwUU~gLj-3C~pK88Tn-VWLl7Yso02*HYpyjMy0|O|G z`)>gJt#MffT>cZT8mre}2%2X+>3^Dk{``+27{nA35#BZp)`aIa7j;8Gwr@36zBqR8 z=hd88Jwj7I8cCAD@*YEVRZtL_mHSZ6)La1cuCgJw<-_xRS}@rFeg9vMw8uHshrd44 z#Sgxm|DPiM$BwmM_ccAr51MQM&&K`_Xa5}^l)Ft^iInnmpbE}t|F=9~geC~|^edfpKsr2`k(vee|1&=s-T`8eQ-A9G6&K!qRrel zFoUqL3ye%nP35;z)6%NL;imPx{k-nKByTvTcN`A#rQicUji0W+%Ec&vKU;aR!)%y% z(?DK&aDXHsplaS@Le?Q;(CgB*E7`HS5a2R>#4UfPrZQ#wTa(VeV#C{v+F#LF+1n@f zB5P^a_g$tCgtLsgzb2)bsijx7>YU8lGTx069a>Hy{5;p$dxyZkps(ln%ymNl{`!&vg)*Ly!&?cX z@uvE8?4H!jewprGG6rTl<%w;)f6rZ$g~>+9+JVQ%S&x9u-r{DOyO>#t9_;^S%)-GM z^QFooP2%l?tf=lAriM?=p50`1#jRWlygn1^PT{WK?dJ z2+(j(HcB7%zFCJq;o<}T4pqIO<>?yynV)uGf(bEe$mpqA@ZUMXOjkR00$?sz@XN2` zq&OI>(8`Q`e&x<8akO!NnOk9ac^Ayo8r?T&5x~Jb5D}1MP4TRQzcOCT1l%K-eF=JKI$1$edcp?eE-hb1&jqvzQJQ19z=%=^HoMDdOFjrx~QfZ%s=bV<2by{$YO zYId^1z?r~6f)a3;#q>=!7?fSv^9~g#sEK3F_<&dD1y1eqc=i=MaYx5x1<>=zBu1tp zy6(Ge_Gk-<3^_S@(VdiMnNWOd*q%q5`Bw5pn8PUmU=KvTXY0LLY9hTWpdVFLGC>B* zEJs3(Q64q5Q2r}b+r^KjM&#JCeFl~{m-Xtfe#$@sqw_}av3+CT_*GMA%DarY?VcYCcJVbnva;QyLuU8g2wg81 z-rW%{kUJRN+bLT#u5|yg;*cF1Ixnxf=OfyCKZfJ-LypX1&>bF7{>H@Z2s4z~QIu@` zUW~QrtuZw#lEiuv;)Ix*s>AL@wVtCDv&AI4A&x*)iZpj6*_{-o&}Wgev0}UHtg1hl zulT=e)EpYl0gbC6~(XUn3+NbLFc#kJ?u8z(hR)*h9ym5CN zNys5Ci~aLqCh}_vlb12=^Ei({M@SaJYg{i+*0k(oeG>@cTDH7W75#0vexhjnxz}Jq zf?DRL_se5u3IY9Z`jr9Gbs7~f)g3EU9vsERNoQ@ZznYD#dmb7aDibKi9ald$AoYxz zlpmW+X?sK8H~WwMXnUiQ)9VLilWK){ZaW zxvi{}u#8!qRuqdf3Jhs3ZM$ioBu#@nUyy^mW$u+&WMo`T?Xur^z@s!gzPMIz9{2d2 zRdIuGDYj>fU9}Y{>lcVHkHuELomCDZNrOqB2Z|o^eL%5KatgJNZ>xpEn@}^ee*{#M zoo!|+C1{X&D+F=6P1U=nK<{vNXG(Of!g#JX>1qGzbthy3>@;ERk?7-*@=f3>ChbDH zi^T08=pY-t3vLUX3$)7^PB5e(@-cVzfi)9?GHhC8+H(0oGq9OV*a@7%`cK&Np@ti3 zxZ!%dA3V1D5HuuF!#uN}+iNVv?u!PwguNpXy3Li=wSW&FTkA%fN=%KTq%JZ@q3C=1 zn6<(KD60e8Eo%1SG@sFF``A|-053IlD@s_k->ECTcRzK+9~G9{Wy)_~&oqSgdl?v7 zhRrQnK6RwLJS^o%b|FeV{hM&ih(EZO`8LNwno@v4y#d*Ev8hdJz!Fi<2Dst}A6?$= z=aECO`{T1VxyZ6|)$^S2%+#-g@IEZeCpSW2?!ju`o&i~eqn<9KaEbcE-}Ni?guT<+ z<3qG?lm*dk+8R)uWaQt%-7_vc)E4=e2M^8u5p@U=u^_O3@{j`}Y%fPY6vY((sS+vu z;#|Dgt=vC^3XdkA*j=Ra#>P)u9}C$XFS7LZMlhgK70{DqMuYPrahV<^=8>&B?E0+A?MY+uSk@TVu z88x$7^473PV9XvClkZRfwF~ds7iPimHQ+pNj_G2^Red^1S2~M#YbqnycJR2zzhhJo zChSDvih#WfR-2#cHak4Th#P9!!?xEA=LVl(*h|mMVdB*HzwK&1p+Ja6xZ$3Y$2?J| z65YM5ZADzN)p zUoyt1irmW`9#K>&v`7-r6buTXw!AKo%SGQ{ro@sj&XKkdoKq8T2dE2x_~SxBxb(2pCgL8<+H1Ts1g%s5wIQ`YRd{)B*CRm+3Hn-WJP1 zR;m)-mYM{1q}GbRZuBM*ICx5pG@$&QpY@XtwaiTl;Eit^h1GW{(N#Qyfud$jqXvdx zpVUHPr%WZsitNVl4lg%Q89(>#_iA{U_Q!(!$k zE8lP1J3HJm$=j55DP~stYQ2*z4Ug$ntS)mJMB<0a)PRPc@O<)g>Nj6>(OFwoO+9v; z2f3wZl)0sZPe#A6^MKyxC-WUD!jKhb8p@887~1W85AE`wrJ_a~9JYcN+G(d9OHL{I zvjy}ks7H98EPlE>9^nPyf(j?LnM#2QI2qL8&tq2=b!`QKlCbWIf9U|HZ&%+F00=Sg ziG)8(+`7lr<%Iq^i_xERHSb6R(;IuJDj*^*5pV3p=YWQnHKNd4(`N?e>jT*SqNoHV zCW12l)XqWBz6lpRq|&r=xWJco0mwaR;=G4Os&!0*p4h7WZF@%U<~&`4b<+WZ+Y0u7$^Z842LC@+OvKZbJp z6WE0gmge}HPx*Rhr_ko!J_*$aO|FZzSfv=No>te^+`5prf(&%vO?!5V`Xy$udP4OIpD5mJOc<3h8rc)h|>Yd!TogY*aC(Vn=V z?v19weIVY4p>bMV&cdN2V1CIZdPo`sdwE!p7OrDW{(0YAF0Dtg0?{hnJ5Sl6bF?gC z6jJ}?ulwb2W!sS*ui3kF{f01sV~&{H6UoJ0;*xfV ztgko;7eWOXR;ZoiCOvCM)&S4jJqB!UhA+O15;k5!E_Ak%5DpHTV1NQgD zjv?J4Fc%!2*@5iRFN}fFb|j|G-fPY-1G~M{9B96b3bdbkS7q-ld(C06O=Zf!QE$>;?#OH`u~e@oiX&S# zD3PImcp;h#v<4D~SxKP{AD-xZ1yO&Y>CKI&Cb|TjSI5jlqoq#Q;dk0fgNI=7MDNr4 zSAy00k)aPv5C3RAZGCtiw&{IteOkmJLl;;H!G%HiJN9^)RH94oh9j`^%R^h^G7Fe` zKlSc!m0!P5C38k;jd4{*iS{poHlKvIRgG&gS?A+}Xx6FPZ^~RLSyQM^X=6aDx4E!~ z)}vjNV-z|pdh5#^#tnb@XjHx%NrVE1>iqoB-6ADEMcn`r3O`|O9}?5GzEU5BxCRjZ zdF`)OIMeaGF7JLQva^H(d$WdnI{ZP-%9aL2ScS=rE370=Nz!n-`;n23Q?=I%E*)PO zQsYZc-qvkBJA?0^Qp+1n$CH)M_w%66>rzRHLJP|$e%}xiC@F!G)pfqwa^7~1rp>%> z^~*rXb_JMJmVGq?6Uul0!}H7oZK8Q>)&UC!z+9R>U1@8FvEnPsU0o~->+otTgUZ{>8c@3gDY>cqtevu>;g7}G((-cCT+EovYfCV@ z1v1=BIc(-_oUAWfdUUX~PY$91zej;KNc!3zP!RYkY<^$_R{%CvV64?q}Xh|3(A10WN=*aU?6b;$f@^%rn@FA8;_4 z>WzfG3_qJ`A9r1jM7`V6q(B0$%OAORe!H7rw_2N90Om;ay%wOS$> zibjUcZttxYM-82BGmJRO|9=Bz)veO=M?0p zF07GQJ$=yP?ptAM?YK`#FD_AeSwA`=Hy@v*1XxX(c?7(L!bC;Ufi%daU_(`HTL@&n zIw)lO+h6etd5dC@`#6yS$NXeA>0~b-1V{t%dPPIcx%wB^)zf$P^c!NhsA=4|QFiQ8 zWRC1Os6WQNyij$6lAo(=(apeKFg^s_*^=_|+ZhJ$4g>sd0|SZ*sF?NTBk5*biqJsL zrngu?M6pG=8i!-mpC#FtYvK|`igXOXD{VA}%|1H6raq>3u>9HYO<$15Szji5Eczy+ zcTKHk^|4e;C`|R}SZ8@uxdmM*8# z(;0!jr`XnMzuVOO4~aeYw^)-K(S@>v{E~{cSt&tw?holi6Be5q$u6?_o%z4R*I(V= zkQ|B@o1~fBIJqJSj>-_Cv?$4~CMsl3-ObsR&8J>@z+ab}BNxI!&P}Xn0QaKGEW=e)>(ScC`5x&3f9+Kvn}< zXLm;D0+6Y4K3IsxK+Wu=&5}Z4h4VneSHE(rCONJ``zi7GwG_|YV;ii(Ta@Mgd?B>e z4<2X`#Unkk^ga(H$xPMK%TX|we7y&_od?^LK^kc`FV9fub`;u+_^O2S%zYLA^;C*R zW%l7)0PS3_bIYs8tVcC-a{y-2?Fny^*qGoMPuXHB)1WXUclTK#LK?avKhG^SYwwPR zt?Prl+5qmNF`YlqdDXEl04El3IIB2OGjjG2Zqt#gV4fJpm^5n0hfgrok4a{eY_1>v zQZKCG{@gdZzzhHW{zxo1gXT_a+LQ*8+uJWoCXW~UzJ0ZR(5bmT^;`1ul*?hjbo^Jf zg{_enMK0@XHw!7Vv~|=ztlsFKH#r6BJkw@<>7@@u)%SjMBWN@dg-~ctd}?Onh`qzau_|lXJdb zhDbfD^^>i_oSfV`_up@IbzZo7DCRxE1J`sq-l{ZZDM=9&h-RId3hM@od5T)6euMJ{}^tE7%9X+(`(|r&UiEg_}S$ zpS|D;X#kn2`6kl)K15C#iVTH8MtK$@5mj9TNikZ(klov