Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3defc9da95 | |||
| ff75dacfb6 | |||
| bf7a454ad0 |
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bu3eevt6f31vy"
|
||||
path="res://.godot/imported/intro_seq_0001.png-d13407f069897f5b8302350ea06caad2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0001.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0001.png-d13407f069897f5b8302350ea06caad2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cv0386ujbone6"
|
||||
path="res://.godot/imported/intro_seq_0002.png-7d720fee04fce498d05e156084b3ef90.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0002.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0002.png-7d720fee04fce498d05e156084b3ef90.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bpagtmd32idd0"
|
||||
path="res://.godot/imported/intro_seq_0003.png-ae551e25a09b19cb262685550214d751.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0003.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0003.png-ae551e25a09b19cb262685550214d751.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6rdw1pvnq4cj"
|
||||
path="res://.godot/imported/intro_seq_0004.png-9977a1ecdd9ddac47ca83665f2c41e57.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0004.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0004.png-9977a1ecdd9ddac47ca83665f2c41e57.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bwhtngfrq8ao7"
|
||||
path="res://.godot/imported/intro_seq_0005.png-492c659aed88c33763898b51cdecde09.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0005.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0005.png-492c659aed88c33763898b51cdecde09.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d4c6etc2k8o5b"
|
||||
path="res://.godot/imported/intro_seq_0006.png-754b5919bb8ea2d96bfce5d29bfa03af.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0006.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0006.png-754b5919bb8ea2d96bfce5d29bfa03af.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://buw18qc3wanyf"
|
||||
path="res://.godot/imported/intro_seq_0007.png-5924916944ea882df411d192e1106616.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0007.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0007.png-5924916944ea882df411d192e1106616.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://rqsffoxigmul"
|
||||
path="res://.godot/imported/intro_seq_0008.png-cc0a32cbed495eedfc84f56240d77d70.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0008.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0008.png-cc0a32cbed495eedfc84f56240d77d70.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dgqqdvsv087dq"
|
||||
path="res://.godot/imported/intro_seq_0009.png-9477102b81fd9f557f19c0091825e489.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0009.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0009.png-9477102b81fd9f557f19c0091825e489.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dx1eu7pdngt2b"
|
||||
path="res://.godot/imported/intro_seq_0010.png-d159843ba1107baa767e0f568ebe5164.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0010.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0010.png-d159843ba1107baa767e0f568ebe5164.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://hrpawt4pm8vw"
|
||||
path="res://.godot/imported/intro_seq_0011.png-da361e3c03d7c9e37aafa9a94fce40aa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0011.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0011.png-da361e3c03d7c9e37aafa9a94fce40aa.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cwxwxuq1qvq1s"
|
||||
path="res://.godot/imported/intro_seq_0012.png-994091f05e3fffd9558b9c023b1e8959.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0012.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0012.png-994091f05e3fffd9558b9c023b1e8959.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dcatel76pvhaf"
|
||||
path="res://.godot/imported/intro_seq_0013.png-4f4f9ef778d5ed3a8f18e94c837f6258.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0013.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0013.png-4f4f9ef778d5ed3a8f18e94c837f6258.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
|
Before Width: | Height: | Size: 12 MiB |
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c0pwx26aill3t"
|
||||
path="res://.godot/imported/intro_seq_0016.png-ad89023f08798b251f84156b0d00d183.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0016.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0016.png-ad89023f08798b251f84156b0d00d183.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b8mbxrarmnu70"
|
||||
path="res://.godot/imported/intro_seq_0017.png-5bba2681811780a47d9cb4b03c476a91.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0017.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0017.png-5bba2681811780a47d9cb4b03c476a91.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bmgpk0egfy66u"
|
||||
path="res://.godot/imported/intro_seq_0018.png-b04a1bd60a6eb8446f8a91aca3abae15.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0018.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0018.png-b04a1bd60a6eb8446f8a91aca3abae15.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dt41tdv4ameam"
|
||||
path="res://.godot/imported/intro_seq_0019.png-e64a687201179a236907ea9ab3080cbd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0019.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0019.png-e64a687201179a236907ea9ab3080cbd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dqdytcgsacjxc"
|
||||
path="res://.godot/imported/intro_seq_0020.png-94a72e7452125cc098d4df663548b117.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0020.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0020.png-94a72e7452125cc098d4df663548b117.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3u6yoiw4anm0"
|
||||
path="res://.godot/imported/intro_seq_0021.png-e41ed967859539364420056b92742e4b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0021.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0021.png-e41ed967859539364420056b92742e4b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cl2ni7jmhpdj6"
|
||||
path="res://.godot/imported/intro_seq_0022.png-147d9f81538df8bcb3842e1b12917298.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0022.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0022.png-147d9f81538df8bcb3842e1b12917298.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bx77btneo2n5o"
|
||||
path="res://.godot/imported/intro_seq_0023.png-d79bc8b0b5339953b97138b03091c590.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0023.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0023.png-d79bc8b0b5339953b97138b03091c590.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b5uwi1h64dqek"
|
||||
path="res://.godot/imported/intro_seq_0024.png-4ae491dbd3e8b33a50bf2e9fc7c44e36.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0024.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0024.png-4ae491dbd3e8b33a50bf2e9fc7c44e36.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://uv6nf2cgqjyy"
|
||||
path="res://.godot/imported/intro_seq_0025.png-99f570fe058fb7903ce72e35a3c938f8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0025.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0025.png-99f570fe058fb7903ce72e35a3c938f8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://fh7m5y5j336x"
|
||||
path="res://.godot/imported/intro_seq_0026.png-5bb48d75704d345d874831e5f6a2b446.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0026.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0026.png-5bb48d75704d345d874831e5f6a2b446.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dfmr5syqlie06"
|
||||
path="res://.godot/imported/intro_seq_0027.png-436ad22542c38673365c1700684b8278.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0027.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0027.png-436ad22542c38673365c1700684b8278.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bx71y7cf6lsse"
|
||||
path="res://.godot/imported/intro_seq_0028.png-250f5531a5db7406a3d95fa6f5a4b753.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0028.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0028.png-250f5531a5db7406a3d95fa6f5a4b753.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://r8vet7ghanp1"
|
||||
path="res://.godot/imported/intro_seq_0029.png-deaafc16edf139dfe84ed9c86c1fc7ef.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0029.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0029.png-deaafc16edf139dfe84ed9c86c1fc7ef.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://4b8irulowbbv"
|
||||
path="res://.godot/imported/intro_seq_0030.png-5860e05ac89ecb6e2036d6a84ee44e4e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0030.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0030.png-5860e05ac89ecb6e2036d6a84ee44e4e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cvf5isldc65n4"
|
||||
path="res://.godot/imported/intro_seq_0031.png-927f6fff06201f79eaf5ced54934d825.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0031.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0031.png-927f6fff06201f79eaf5ced54934d825.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d4j88tyqprs2d"
|
||||
path="res://.godot/imported/intro_seq_0032.png-fb16b2b570af97548a9a76fe074d0679.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0032.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0032.png-fb16b2b570af97548a9a76fe074d0679.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b1k4bojvgeksu"
|
||||
path="res://.godot/imported/intro_seq_0033.png-46cbba009bcd114946dc61b4952e390f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0033.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0033.png-46cbba009bcd114946dc61b4952e390f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 12 MiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://7cyyof1476yl"
|
||||
path="res://.godot/imported/intro_seq_0034.png-e9bc2dbf0d75b6ff302c4cc0bfef8e97.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0034.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0034.png-e9bc2dbf0d75b6ff302c4cc0bfef8e97.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -96,8 +96,22 @@ godot --headless --script tests/gameplay_test.gd # full scene, bone sanity,
|
||||
# Gameplay test with real rendering (inspect frame strip visually):
|
||||
godot --script tests/gameplay_test.gd # opens a window, saves real screenshots
|
||||
# Output: tests/output/gameplay/motion_00..04.png, ragdoll_trigger.png, ragdoll_result.png
|
||||
|
||||
# Difficulty simulation (balance tool, ~2 min):
|
||||
godot --headless --script tests/difficulty_sim.gd # drives a bot bull vs one matador
|
||||
```
|
||||
|
||||
### Difficulty simulation (`tests/difficulty_sim.gd`)
|
||||
A balance tool, not a pass/fail test. It drives a kinematic "bot bull" against one
|
||||
real matador across behaviour profiles (CHARGER / KITER / PASSIVE) and reports the
|
||||
matador's win rate (how often it gores the bull). Use it to tune matador difficulty
|
||||
from data. The bot bull has NO abilities (it only kills by ramming), so it can't use
|
||||
roll/slam/kick — real skilled play beats the matador more than these numbers imply;
|
||||
read the rates as "how well the matador threatens each movement pattern." A rough
|
||||
target is CHARGER high (blind charging is punished), KITER moderate (throws are a
|
||||
dodgeable threat), PASSIVE high (standing still dies). Note: as a `--script` entry it
|
||||
reads the `DP` autoload via `/root/DP`, not the global identifier.
|
||||
|
||||
### What the gameplay test catches
|
||||
| Check | What it detects |
|
||||
|---|---|
|
||||
|
||||
|
After Width: | Height: | Size: 144 KiB |
@@ -2,16 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ypxkr3vla3fr"
|
||||
path="res://.godot/imported/HUD_0000_ability_JALG.png-c430df7817ae1f22045a2a27b06f60a8.ctex"
|
||||
uid="uid://cyscxku0nw055"
|
||||
path="res://.godot/imported/DASH.png-02cbc39ec03a0c79526d7dc333659c48.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://HUD/HUD_0000_ability_JALG.png"
|
||||
dest_files=["res://.godot/imported/HUD_0000_ability_JALG.png-c430df7817ae1f22045a2a27b06f60a8.ctex"]
|
||||
source_file="res://HUD/DASH.png"
|
||||
dest_files=["res://.godot/imported/DASH.png-02cbc39ec03a0c79526d7dc333659c48.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dnv8w5h32ck7m"
|
||||
path="res://.godot/imported/HUD_0002_ability_DASH.png-011f272dda65c06c407720ec201ad92e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://HUD/HUD_0002_ability_DASH.png"
|
||||
dest_files=["res://.godot/imported/HUD_0002_ability_DASH.png-011f272dda65c06c407720ec201ad92e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 168 KiB |
@@ -2,16 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://w4yovxyg0104"
|
||||
path="res://.godot/imported/HUD_0001_ability_SLAM.png-7c5f88306e1af474db2c2073888feab7.ctex"
|
||||
uid="uid://bdnc3jw1ykydi"
|
||||
path="res://.godot/imported/KICK.png-5482be80e8176ed7a0ae69a48ca1f5ed.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://HUD/HUD_0001_ability_SLAM.png"
|
||||
dest_files=["res://.godot/imported/HUD_0001_ability_SLAM.png-7c5f88306e1af474db2c2073888feab7.ctex"]
|
||||
source_file="res://HUD/KICK.png"
|
||||
dest_files=["res://.godot/imported/KICK.png-5482be80e8176ed7a0ae69a48ca1f5ed.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
After Width: | Height: | Size: 204 KiB |
@@ -2,16 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://x8eewdg65qgr"
|
||||
path="res://.godot/imported/intro_seq_0014.png-f8346c8267324ffe151c42113f79700d.ctex"
|
||||
uid="uid://d25i88cixy4pn"
|
||||
path="res://.godot/imported/ROLL.png-306dafcb4b6c13eded9a925aa6d2f62a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0014.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0014.png-f8346c8267324ffe151c42113f79700d.ctex"]
|
||||
source_file="res://HUD/ROLL.png"
|
||||
dest_files=["res://.godot/imported/ROLL.png-306dafcb4b6c13eded9a925aa6d2f62a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
After Width: | Height: | Size: 80 KiB |
@@ -2,16 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://nw2pvct1kqcd"
|
||||
path="res://.godot/imported/intro_seq_0015.png-1a3c8239c9c1c61cd711f23fcc29af50.ctex"
|
||||
uid="uid://bxplqrp4mc5yh"
|
||||
path="res://.godot/imported/SLAM.png-b5036a739251be1240a24080e28d7a8e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Animations/Intro_sequence/intro_seq_0015.png"
|
||||
dest_files=["res://.godot/imported/intro_seq_0015.png-1a3c8239c9c1c61cd711f23fcc29af50.ctex"]
|
||||
source_file="res://HUD/SLAM.png"
|
||||
dest_files=["res://.godot/imported/SLAM.png-b5036a739251be1240a24080e28d7a8e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
@@ -15,6 +15,10 @@ var _aerial_pitch: float = 0.0
|
||||
var charge_pitch_target: float = 0.0
|
||||
var _charge_pitch_curr: float = 0.0
|
||||
|
||||
# When true, another system (the roll's ball-tumble) owns the bull's full
|
||||
# orientation — skip writing body wobble so we don't fight it.
|
||||
var external_rotation: bool = false
|
||||
|
||||
# Tail — Verlet chain simulation
|
||||
const TAIL_BONES: Array = [
|
||||
&"tail_1", &"tail_2", &"tail_3", &"tail_4",
|
||||
@@ -128,6 +132,8 @@ func _wobble_body(delta: float) -> void:
|
||||
_aerial_pitch = lerpf(_aerial_pitch, target_pitch, delta * DP.f("aerial_pitch_speed"))
|
||||
|
||||
_charge_pitch_curr = lerpf(_charge_pitch_curr, charge_pitch_target, delta * DP.f("charge_head_speed"))
|
||||
if external_rotation:
|
||||
return
|
||||
_bull.rotation.x = _tilt_x + _aerial_pitch + _charge_pitch_curr
|
||||
_bull.rotation.z = _tilt_z
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@ var _shake_amount: float = 0.0
|
||||
var _shake_decay: float = 0.0
|
||||
var _user_fov: float = 0.0
|
||||
|
||||
# Trailing-camera state: _focus is the smoothed point the camera frames (it lags
|
||||
# the bull), _drag_offset is the velocity-driven trail that slides it behind.
|
||||
var _focus: Vector3 = Vector3.ZERO
|
||||
var _drag_offset: Vector3 = Vector3.ZERO
|
||||
var _focus_init: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_as_top_level(true)
|
||||
@@ -48,11 +54,12 @@ func _process(delta: float) -> void:
|
||||
)
|
||||
|
||||
var target := get_parent() as Node3D
|
||||
var focus := _drag_focus(target, delta)
|
||||
|
||||
global_position = target.global_position + offset
|
||||
look_at(target.global_position, Vector3.UP)
|
||||
global_position = focus + offset
|
||||
look_at(focus, Vector3.UP)
|
||||
|
||||
_update_dof(target.global_position)
|
||||
_update_dof(focus)
|
||||
|
||||
if _shake_amount > 0.005:
|
||||
global_position += Vector3(
|
||||
@@ -64,6 +71,26 @@ func _process(delta: float) -> void:
|
||||
_shake_amount = move_toward(_shake_amount, 0.0, _shake_decay * delta)
|
||||
|
||||
|
||||
# Smoothed follow point that drags behind the bull. The target trail sits opposite
|
||||
# the bull's flat velocity (cam_drag metres per m/s); the offset and the focus both
|
||||
# ease toward their targets so the camera lags and settles rather than snapping.
|
||||
func _drag_focus(target: Node3D, delta: float) -> Vector3:
|
||||
var pos := target.global_position
|
||||
if not _focus_init:
|
||||
_focus = pos
|
||||
_focus_init = true
|
||||
|
||||
var vel := Vector3.ZERO
|
||||
if target is CharacterBody3D:
|
||||
vel = (target as CharacterBody3D).velocity
|
||||
vel.y = 0.0
|
||||
|
||||
var drag_target := -vel * DP.f("cam_drag")
|
||||
_drag_offset = _drag_offset.lerp(drag_target, clampf(DP.f("cam_drag_speed") * delta, 0.0, 1.0))
|
||||
_focus = _focus.lerp(pos + _drag_offset, clampf(DP.f("cam_follow_lerp") * delta, 0.0, 1.0))
|
||||
return _focus
|
||||
|
||||
|
||||
func trigger_hit(strength: float) -> void:
|
||||
_shake_amount = strength * 0.5
|
||||
_shake_decay = _shake_amount / 0.4
|
||||
|
||||
@@ -387,12 +387,15 @@ func _reconcile(delta: float) -> void:
|
||||
if scene != null:
|
||||
_collect(scene, col, hit, bone, anim, wanted)
|
||||
|
||||
# Drop overlays whose shape is gone or no longer wanted.
|
||||
# Drop overlays whose shape is gone or no longer wanted. The overlay is a child of
|
||||
# the collision shape, so it's freed with it — hold it as Variant, since assigning a
|
||||
# freed instance to any *typed* var (even Object) throws before is_instance_valid()
|
||||
# can filter it out.
|
||||
for id: int in _overlays.keys():
|
||||
var mi: MeshInstance3D = _overlays[id]
|
||||
var mi: Variant = _overlays[id]
|
||||
if not wanted.has(id) or not is_instance_valid(mi):
|
||||
if is_instance_valid(mi):
|
||||
mi.queue_free()
|
||||
(mi as Node).queue_free()
|
||||
_overlays.erase(id)
|
||||
# Add overlays for newly seen shapes.
|
||||
for id: int in wanted:
|
||||
@@ -442,9 +445,9 @@ func _attach(cs: CollisionShape3D, is_area: bool) -> MeshInstance3D:
|
||||
|
||||
func _clear_overlays() -> void:
|
||||
for id: int in _overlays:
|
||||
var mi: MeshInstance3D = _overlays[id]
|
||||
var mi: Variant = _overlays[id]
|
||||
if is_instance_valid(mi):
|
||||
mi.queue_free()
|
||||
(mi as Node).queue_free()
|
||||
_overlays.clear()
|
||||
|
||||
|
||||
|
||||
@@ -22,25 +22,17 @@ func _ready() -> void:
|
||||
|
||||
func _register_all() -> void:
|
||||
# ── Movement ──────────────────────────────────────────────────────────────
|
||||
_reg_f("Movement", "thrust_impulse", 18.0, 1.0, 200.0)
|
||||
_reg_f("Movement", "thrust_vertical", 2.0, 0.0, 20.0)
|
||||
_reg_f("Movement", "thrust_min_angle", 15.0, 0.0, 90.0, 1.0)
|
||||
_reg_f("Movement", "thrust_max_angle", 90.0, 0.0, 180.0, 1.0)
|
||||
_reg_f("Movement", "thrust_charge_time", 0.5, 0.05, 2.0, 0.05)
|
||||
# WASD/arrows drive the bull: move_accel ramps flat velocity toward max_speed in
|
||||
# the input direction; move_friction bleeds it off when no direction is held.
|
||||
_reg_f("Movement", "max_speed", 50.0, 2.0, 1000.0)
|
||||
_reg_f("Movement", "roll_damping", 0.15, 0.01, 0.99, 0.01)
|
||||
_reg_f("Movement", "move_accel", 120.0, 5.0, 600.0)
|
||||
_reg_f("Movement", "move_friction", 90.0, 5.0, 600.0)
|
||||
_reg_f("Movement", "jump_velocity", 4.5, 1.0, 200.0)
|
||||
_reg_f("Movement", "visual_turn_speed", 12.0, 1.0, 200.0)
|
||||
_reg_f("Movement", "bounce_restitution", 0.55, 0.0, 2.0, 0.05)
|
||||
_reg_f("Movement", "bounce_min_impact", 1.0, 0.0, 20.0)
|
||||
_reg_f("Movement", "wall_bounce_restitution", 0.6, 0.0, 1.5, 0.05)
|
||||
_reg_f("Movement", "wall_min_bounce_speed", 6.0, 0.0, 30.0)
|
||||
_reg_f("Movement", "kick_impulse", 8.0, 1.0, 50.0)
|
||||
_reg_f("Movement", "kick_interval", 0.25, 0.05, 1.0, 0.01)
|
||||
_reg_f("Movement", "kick_vertical", 0.0, 0.0, 15.0)
|
||||
_reg_f("Movement", "kick_force_var", 0.2, 0.0, 1.0, 0.01)
|
||||
_reg_f("Movement", "kick_spread", 10.0, 0.0, 45.0, 1.0)
|
||||
_reg_f("Movement", "charge_kick_mult", 2.0, 1.0, 5.0)
|
||||
# ── Dust speed thresholds ─────────────────────────────────────────────────
|
||||
_reg_f("Dust", "dust_walk_spd", 1.5, 0.1, 10.0)
|
||||
_reg_f("Dust", "dust_charge_spd", 7.0, 1.0, 20.0)
|
||||
@@ -63,10 +55,7 @@ func _register_all() -> void:
|
||||
_reg_f("Tail", "tail_body_radius", 0.35, 0.05, 2.0, 0.05)
|
||||
_reg_f("Tail", "tail_wag_freq", 4.0, 0.5, 15.0)
|
||||
_reg_f("Tail", "tail_wag_idle", 0.3, 0.0, 2.0, 0.05)
|
||||
# ── Charge (both mouse buttons) ──────────────────────────────────────────
|
||||
_reg_f("Charge", "bull_charge_accel", 80.0, 1.0, 400.0)
|
||||
_reg_f("Charge", "bull_charge_max_speed", 76.0, 5.0, 200.0)
|
||||
_reg_f("Charge", "charge_ramp_time", 0.75, 0.1, 5.0, 0.05)
|
||||
# ── Charge pose (horns-down lean during dash / roll) ─────────────────────
|
||||
_reg_f("Charge", "charge_head_pitch", 0.3, 0.0, 1.2, 0.01)
|
||||
_reg_f("Charge", "charge_head_speed", 5.0, 0.5, 20.0)
|
||||
# ── Body ──────────────────────────────────────────────────────────────────
|
||||
@@ -96,10 +85,21 @@ func _register_all() -> void:
|
||||
_reg_f("Camera", "cam_spring_length", 7.0, 1.0, 30.0)
|
||||
_reg_f("Camera", "cam_min_vert_angle", -60.0, -90.0, 0.0, 1.0)
|
||||
_reg_f("Camera", "cam_max_vert_angle", 45.0, 0.0, 90.0, 1.0)
|
||||
# Trailing "drag" camera: the focus point lags the bull and slides opposite to
|
||||
# its velocity, so the camera visibly drags behind during a fast charge.
|
||||
# cam_drag = metres of trail per m/s of bull speed (0 = rigid follow)
|
||||
# cam_drag_speed = how fast the trail offset eases toward its target
|
||||
# cam_follow_lerp = how tightly the focus point chases the bull (higher = snappier)
|
||||
_reg_f("Camera", "cam_drag", 0.12, 0.0, 1.0, 0.01)
|
||||
_reg_f("Camera", "cam_drag_speed", 3.5, 0.5, 20.0)
|
||||
_reg_f("Camera", "cam_follow_lerp", 10.0, 1.0, 40.0)
|
||||
# ── Matador ───────────────────────────────────────────────────────────────
|
||||
_reg_f("Matador", "mat_spawn_count", 1.0, 1.0, 30.0, 1.0)
|
||||
_reg_f("Matador", "mat_walk_speed", 4.0, 0.5, 10.0)
|
||||
_reg_f("Matador", "mat_walk_speed", 5.5, 0.5, 10.0)
|
||||
_reg_f("Matador", "mat_wander_radius", 22.0, 5.0, 50.0)
|
||||
# Radius of the inner fighting floor: bull + matadors spawn on this ring (so
|
||||
# they stay off the benches). Tune in the console to hug the bench edge.
|
||||
_reg_f("Matador", "arena_spawn_radius", 10.0, 2.0, 22.0, 0.5)
|
||||
_reg_f("Matador", "mat_idle_min", 0.5, 0.0, 5.0, 0.1)
|
||||
_reg_f("Matador", "mat_idle_max", 2.5, 0.5, 10.0, 0.1)
|
||||
_reg_f("Matador", "mat_hit_threshold", 4.0, 1.0, 30.0)
|
||||
@@ -110,33 +110,55 @@ func _register_all() -> void:
|
||||
_reg_f("Matador", "mat_joint_twist", 40.0, 0.0, 180.0, 1.0)
|
||||
_reg_f("Matador", "mat_ragdoll_damp", 1.5, 0.0, 10.0, 0.1)
|
||||
_reg_f("Matador", "mat_flee_range", 3.5, 2.0, 40.0)
|
||||
_reg_f("Matador", "mat_flee_speed", 5.5, 0.5, 12.0)
|
||||
_reg_f("Matador", "mat_flee_speed", 7.0, 0.5, 12.0)
|
||||
_reg_f("Matador", "mat_charge_speed", 10.0, 1.0, 30.0)
|
||||
_reg_f("Matador", "mat_brace_range", 5.0, 2.0, 20.0)
|
||||
_reg_f("Matador", "mat_brace_duration", 0.7, 0.05, 1.5, 0.05)
|
||||
_reg_f("Matador", "mat_commit_dist", 2.5, 0.5, 6.0, 0.1)
|
||||
_reg_f("Matador", "mat_step_speed", 3.5, 2.0, 20.0)
|
||||
_reg_f("Matador", "mat_step_speed", 6.5, 2.0, 20.0)
|
||||
_reg_f("Matador", "mat_step_duration", 0.35, 0.1, 1.0, 0.05)
|
||||
# How strongly the sidestep biases toward the bull so the blade sweeps through
|
||||
# its path during the pass (0 = pure lateral dodge, 1 = straight at the bull).
|
||||
_reg_f("Matador", "mat_pass_lunge", 0.35, 0.0, 1.0, 0.05)
|
||||
_reg_f("Matador", "mat_dodge_cooldown", 2.5, 0.5, 12.0, 0.25)
|
||||
_reg_f("Matador", "mat_dodge_chance", 0.55, 0.0, 1.0, 0.05)
|
||||
_reg_f("Matador", "mat_dodge_chance", 0.72, 0.0, 1.0, 0.05)
|
||||
_reg_f("Matador", "mat_attack_range", 8.0, 2.0, 20.0)
|
||||
_reg_f("Matador", "mat_attack_speed", 5.5, 1.0, 12.0)
|
||||
_reg_f("Matador", "mat_attack_speed", 7.5, 1.0, 12.0)
|
||||
_reg_f("Matador", "mat_attack_duration", 1.8, 0.5, 5.0, 0.1)
|
||||
_reg_f("Matador", "mat_attack_min_dist", 3.0, 0.5, 6.0, 0.1)
|
||||
_reg_f("Matador", "mat_attack_min_dist", 3.2, 0.5, 6.0, 0.1)
|
||||
# Forward drive during the opening of a melee swing — turns the stationary stab
|
||||
# into an estocada lunge so the blade covers ground toward the bull.
|
||||
_reg_f("Matador", "mat_lunge_speed", 9.0, 0.0, 20.0)
|
||||
_reg_f("Matador", "mat_lunge_time", 0.18, 0.0, 0.6, 0.01)
|
||||
_reg_f("Matador", "mat_lunge_speed", 13.0, 0.0, 20.0)
|
||||
_reg_f("Matador", "mat_lunge_time", 0.22, 0.0, 0.6, 0.01)
|
||||
# Reach of the reach-based stab (the reliable melee hit) and its re-strike gap.
|
||||
# The bull dies if it comes inside mat_stab_reach of a matador that's attacking,
|
||||
# bracing, or passing with a drawn blade — closing in is genuinely deadly.
|
||||
_reg_f("Matador", "mat_stab_reach", 1.7, 0.5, 4.0, 0.1)
|
||||
_reg_f("Matador", "mat_stab_cooldown", 0.6, 0.1, 3.0, 0.1)
|
||||
# Half-angle (deg) the matador must be facing the bull within for a stab to land.
|
||||
# Stops "sword's off to the side but I still died" — you're only gored when the
|
||||
# blade is actually presented at you.
|
||||
_reg_f("Matador", "mat_stab_arc", 55.0, 5.0, 180.0, 5.0)
|
||||
_reg_f("Matador", "mat_roll_chance", 0.35, 0.0, 1.0, 0.05)
|
||||
_reg_f("Matador", "mat_roll_duration", 0.45, 0.1, 1.5, 0.05)
|
||||
# Playback rate of the Draw_weapon clip used for both drawing and sheathing —
|
||||
# higher = snappier unholster/holster.
|
||||
_reg_f("Matador", "mat_draw_speed", 1.25, 0.25, 3.0, 0.05)
|
||||
_reg_f("Matador", "mat_draw_speed", 1.9, 0.25, 3.0, 0.05)
|
||||
# Cross-fade between matador animation clips — smooths Run/Attack/Taunt cuts.
|
||||
_reg_f("Matador", "mat_anim_blend", 0.12, 0.0, 0.5, 0.01)
|
||||
# ── Matador heuristics (utility AI) ───────────────────────────────────────
|
||||
# Every mat_decide_interval seconds the matador scores its options — dodge the
|
||||
# pass, close for a strike, retreat, throw the blade, or strut/taunt — and picks
|
||||
# the highest. These weights bias the personality: crank aggression for a reckless
|
||||
# bullfighter, caution for a slippery one. mat_w_commit is a stickiness bonus for
|
||||
# the current choice so it doesn't dither between two near-tied options.
|
||||
_reg_f("Matador", "mat_decide_interval", 0.2, 0.05, 1.0, 0.05)
|
||||
_reg_f("Matador", "mat_w_aggression", 1.4, 0.0, 3.0, 0.05)
|
||||
_reg_f("Matador", "mat_w_caution", 1.0, 0.0, 3.0, 0.05)
|
||||
_reg_f("Matador", "mat_w_flee", 0.8, 0.0, 3.0, 0.05)
|
||||
_reg_f("Matador", "mat_w_showmanship", 1.0, 0.0, 3.0, 0.05)
|
||||
# Small so it smooths dithering without locking the matador into idling.
|
||||
_reg_f("Matador", "mat_w_commit", 0.15, 0.0, 2.0, 0.05)
|
||||
# ── Sword ─────────────────────────────────────────────────────────────────
|
||||
# Local seating of the sword in the right hand (drawn / fighting).
|
||||
# Defaults are identity — the bone is oriented in Blender to seat the sword
|
||||
@@ -157,17 +179,20 @@ func _register_all() -> void:
|
||||
_reg_f("Sword", "sword_rest_rot_y", 0.0, -180.0, 180.0, 1.0)
|
||||
_reg_f("Sword", "sword_rest_rot_z", 0.0, -180.0, 180.0, 1.0)
|
||||
# ── Sword throw (matador ranged attack) ──────────────────────────────────
|
||||
# Probability of committing to a throw each time the matador becomes eligible;
|
||||
# kept low so they favour closing in for a melee Attack over throwing.
|
||||
_reg_f("Sword", "mat_throw_chance", 0.15, 0.0, 1.0, 0.05)
|
||||
# Delay before a fresh sword appears in the holster after one is thrown.
|
||||
_reg_f("Sword", "mat_resword_delay", 2.0, 0.0, 10.0, 0.5)
|
||||
_reg_f("Sword", "mat_throw_range", 18.0, 5.0, 40.0)
|
||||
_reg_f("Sword", "mat_throw_min_dist", 5.0, 2.0, 15.0, 0.1)
|
||||
_reg_f("Sword", "mat_throw_windup", 0.7, 0.2, 2.0, 0.05)
|
||||
_reg_f("Sword", "mat_throw_windup", 0.5, 0.2, 2.0, 0.05)
|
||||
_reg_f("Sword", "mat_throw_release", 0.55, 0.1, 0.95, 0.01)
|
||||
_reg_f("Sword", "mat_throw_speed", 18.0, 5.0, 60.0)
|
||||
_reg_f("Sword", "mat_throw_speed", 24.0, 5.0, 60.0)
|
||||
_reg_f("Sword", "mat_throw_spin", 14.0, 0.0, 40.0)
|
||||
# Gravity scale on the thrown blade (low = flies straight to the aim point) and
|
||||
# the vertical aim offset from the bull origin down onto its low collision body.
|
||||
_reg_f("Sword", "mat_throw_gravity", 0.25, 0.0, 1.0, 0.05)
|
||||
_reg_f("Sword", "mat_throw_aim_y", -0.45,-1.5, 0.5, 0.05)
|
||||
# Aim scatter (± degrees) so thrown blades can be read and dodged on the move.
|
||||
_reg_f("Sword", "mat_throw_spread", 11.0, 0.0, 30.0, 0.5)
|
||||
_reg_f("Sword", "mat_throw_arm_cock", 70.0,-180.0, 180.0, 1.0)
|
||||
_reg_f("Sword", "mat_throw_arm_release", -90.0,-180.0, 180.0, 1.0)
|
||||
_reg_f("Sword", "mat_throw_elbow", 80.0,-180.0, 180.0, 1.0)
|
||||
@@ -186,23 +211,21 @@ func _register_all() -> void:
|
||||
_reg_f("Abilities", "slam_strength", 14.0, 1.0, 30.0)
|
||||
_reg_f("Abilities", "dash_cooldown", 2.0, 0.5, 10.0, 0.1)
|
||||
_reg_f("Abilities", "dash_speed", 66.0, 5.0, 200.0)
|
||||
# ── Roll (boulder charge) ─────────────────────────────────────────────────
|
||||
# A held-momentum roll: inherits your current charge, ramps toward roll_max_speed,
|
||||
# and RICOCHETS off walls with a speed boost (bank shots build ludicrous speed).
|
||||
_reg_f("Roll", "roll_cooldown", 3.0, 0.5, 15.0, 0.1)
|
||||
_reg_f("Roll", "roll_duration", 2.5, 0.5, 6.0, 0.1)
|
||||
_reg_f("Roll", "roll_min_speed", 30.0, 5.0, 120.0) # launch floor (combines charge)
|
||||
_reg_f("Roll", "roll_max_speed", 95.0, 10.0, 200.0) # natural top from acceleration
|
||||
_reg_f("Roll", "roll_accel", 55.0, 0.0, 300.0)
|
||||
# Per-second speed retained above roll_max_speed — low friction so wall-boosted
|
||||
# momentum bleeds off slowly rather than snapping back to the cruise speed.
|
||||
_reg_f("Roll", "roll_momentum", 0.9, 0.1, 0.99, 0.01)
|
||||
_reg_f("Roll", "roll_turn", 3.5, 0.2, 20.0) # steer rate (sluggish = momentum feel)
|
||||
_reg_f("Roll", "roll_wall_boost", 1.25, 1.0, 2.0, 0.05) # speed × on each ricochet
|
||||
_reg_f("Roll", "roll_wall_cap", 150.0, 10.0, 300.0) # ceiling reachable via boosts
|
||||
_reg_f("Roll", "roll_hit_radius", 2.4, 0.5, 6.0, 0.1)
|
||||
_reg_f("Roll", "roll_hit_strength", 22.0, 1.0, 40.0)
|
||||
_reg_f("Roll", "roll_hit_up", 4.5, 0.0, 15.0)
|
||||
# ── Roll (Rammus Powerball) ───────────────────────────────────────────────
|
||||
# The bull curls into a ball and ACCELERATES the longer it rolls, from
|
||||
# roll_base_speed up to roll_max_speed over roll_rampup_time. Turning is wide
|
||||
# (momentum steering via roll_turn). On slamming into a matador the ball POPS:
|
||||
# the target is launched skyward (roll_knockup) and the roll ends.
|
||||
_reg_f("Roll", "roll_cooldown", 3.0, 0.5, 15.0, 0.1)
|
||||
_reg_f("Roll", "roll_duration", 2.5, 0.5, 6.0, 0.1) # max roll time
|
||||
_reg_f("Roll", "roll_base_speed", 28.0, 5.0, 120.0) # speed at the start of the roll
|
||||
_reg_f("Roll", "roll_max_speed", 95.0, 10.0, 200.0) # top speed once fully ramped
|
||||
_reg_f("Roll", "roll_rampup_time", 2.0, 0.2, 6.0, 0.1) # time to reach top speed
|
||||
_reg_f("Roll", "roll_turn", 7.0, 0.2, 20.0) # steer rate (WASD/arrows; higher = tighter)
|
||||
_reg_f("Roll", "roll_spin_radius", 1.4, 0.3, 4.0, 0.1) # ball radius for tumble spin (smaller = faster)
|
||||
_reg_f("Roll", "roll_hit_radius", 2.4, 0.5, 6.0, 0.1) # contact radius that pops the ball
|
||||
_reg_f("Roll", "roll_hit_strength", 22.0, 1.0, 40.0) # horizontal launch on the pop
|
||||
_reg_f("Roll", "roll_knockup", 11.0, 0.0, 25.0) # vertical knock-up on the pop
|
||||
# ── Debug overlays (see debug_draw.gd) ───────────────────────────────────
|
||||
_reg_b("Debug", "show_collisions", false)
|
||||
_reg_b("Debug", "show_hitboxes", false)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://fu84fchl1im6"
|
||||
path="res://.godot/imported/Cinzel.ttf-b9b78caa7497d2ec78b922cb77f34333.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fonts/Cinzel.ttf"
|
||||
dest_files=["res://.godot/imported/Cinzel.ttf-b9b78caa7497d2ec78b922cb77f34333.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://rcfn6xhs7fur"
|
||||
path="res://.godot/imported/CinzelDecorative-Bold.ttf-fd8dd0c7ef2ceb6acaeb4b78a3c0a47b.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fonts/CinzelDecorative-Bold.ttf"
|
||||
dest_files=["res://.godot/imported/CinzelDecorative-Bold.ttf-fd8dd0c7ef2ceb6acaeb4b78a3c0a47b.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cgppsmy8krkhw"
|
||||
path="res://.godot/imported/CinzelDecorative-Regular.ttf-818749a2a58435ecbb00dae02eb6079a.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fonts/CinzelDecorative-Regular.ttf"
|
||||
dest_files=["res://.godot/imported/CinzelDecorative-Regular.ttf-818749a2a58435ecbb00dae02eb6079a.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Cinzel Project Authors (https://github.com/NDISCOVER/Cinzel)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,33 @@
|
||||
class_name GameVersion
|
||||
extends RefCounted
|
||||
## Single source of truth for the game's version string.
|
||||
##
|
||||
## The numeric version lives in project.godot (application/config/version) so it
|
||||
## also feeds export metadata; here we read it back and layer on the pre-release
|
||||
## CHANNEL and a human-readable STAGE label. We're pre-1.0 — every 0.x build is
|
||||
## understood to be unstable, in-development software (see the recommendations in
|
||||
## the commit / PR notes for why 0.x is the convention for early game dev).
|
||||
|
||||
## Pre-release channel appended to the number, SemVer-style (e.g. 0.1.0-alpha).
|
||||
const CHANNEL: String = "alpha"
|
||||
|
||||
## Friendly stage banner shown to players so it's unmistakably an early build.
|
||||
const STAGE: String = "Early Development Build"
|
||||
|
||||
|
||||
## Bare numeric version, e.g. "0.1.0". Read from project settings.
|
||||
static func number() -> String:
|
||||
return str(ProjectSettings.get_setting("application/config/version", "0.0.0"))
|
||||
|
||||
|
||||
## Full version tag, e.g. "v0.1.0-alpha".
|
||||
static func string() -> String:
|
||||
var s := "v" + number()
|
||||
if not CHANNEL.is_empty():
|
||||
s += "-" + CHANNEL
|
||||
return s
|
||||
|
||||
|
||||
## Version tag + stage banner, e.g. "v0.1.0-alpha · Early Development Build".
|
||||
static func full() -> String:
|
||||
return "%s · %s" % [string(), STAGE]
|
||||
@@ -0,0 +1 @@
|
||||
uid://f4bvu1mnvpiu
|
||||
@@ -1,27 +1,28 @@
|
||||
extends CanvasLayer
|
||||
|
||||
const _GOLD := Color(1.00, 0.78, 0.22)
|
||||
const _CREAM := Color(0.88, 0.83, 0.72)
|
||||
const _MUTED := Color(0.65, 0.60, 0.48)
|
||||
const _BG := Color(0.06, 0.04, 0.02, 0.78)
|
||||
const _BADGE := Color(0.16, 0.10, 0.03, 0.90)
|
||||
const _BORDER := Color(0.65, 0.48, 0.15, 0.55)
|
||||
const _GOLD := Color(1.00, 0.78, 0.22)
|
||||
const _CREAM := Color(0.93, 0.88, 0.72)
|
||||
const _MUTED := Color(0.66, 0.60, 0.46)
|
||||
|
||||
# Scoring: each kill scores _KILL_BASE × the live combo count; the combo grows with
|
||||
# every kill inside _COMBO_WINDOW seconds of the last and resets when that lapses.
|
||||
const _KILL_BASE: int = 100
|
||||
const _COMBO_WINDOW: float = 3.0
|
||||
# Per-ability trim colour, tying each tile (and its key cap) to that ability's FX.
|
||||
# Order matches _ABILITY_ICONS: kick, slam, dash, roll.
|
||||
const _ABILITY_ACCENT: Array[Color] = [
|
||||
Color(1.00, 0.62, 0.16), # Kick — ember orange
|
||||
Color(0.98, 0.34, 0.20), # Slam — crimson
|
||||
Color(0.45, 0.75, 1.00), # Dash — sky blue
|
||||
Color(1.00, 0.82, 0.32), # Roll — gold
|
||||
]
|
||||
|
||||
const _ABILITY_ICONS: Array[String] = [
|
||||
"res://HUD/HUD_0000_ability_JALG.png",
|
||||
"res://HUD/HUD_0001_ability_SLAM.png",
|
||||
"res://HUD/HUD_0002_ability_DASH.png",
|
||||
"res://HUD/HUD_0003_ability_ROLL.png",
|
||||
"res://HUD/KICK.png",
|
||||
"res://HUD/SLAM.png",
|
||||
"res://HUD/DASH.png",
|
||||
"res://HUD/ROLL.png",
|
||||
]
|
||||
const _ABILITY_ACTIONS: Array[StringName] = [
|
||||
&"ability_kick", &"ability_slam", &"ability_dash", &"ability_roll",
|
||||
]
|
||||
# Fallback label shown in a slot whose icon file doesn't exist yet (e.g. ROLL).
|
||||
# Fallback label shown in a slot whose icon file fails to load.
|
||||
const _ABILITY_NAMES: Array[String] = ["Kick", "Slam", "Dash", "Roll"]
|
||||
|
||||
var _controls_visible: bool = false
|
||||
@@ -30,20 +31,21 @@ var _toggle_btn: Button
|
||||
var _tab_hint: Label
|
||||
var _spawner: Node
|
||||
var _player: Node = null
|
||||
var _died_connected: bool = false
|
||||
var _cd_overlays: Array[ColorRect] = []
|
||||
var _cd_labels: Array[Label] = []
|
||||
var _health_segments: Array[ColorRect] = []
|
||||
var _health_connected: bool = false
|
||||
var _ability_bar: HBoxContainer
|
||||
var _ability_panels: Array[PanelContainer] = []
|
||||
|
||||
var _score: int = 0
|
||||
var _combo: int = 0
|
||||
var _combo_timer: float = 0.0
|
||||
var _score_label: Label
|
||||
var _wave_label: Label
|
||||
var _combo_label: Label
|
||||
var _combo_track: ColorRect
|
||||
var _combo_fill: ColorRect
|
||||
var _banner_label: Label
|
||||
# Win/lose screen. _game_over latches so the result is shown once; _result_win
|
||||
# records which way it went (read by the gameplay test).
|
||||
const _WIN_VIDEO := "res://videos/bull_win.ogv" # the bull triumphs
|
||||
const _LOSS_VIDEO := "res://videos/matador_win.ogv" # the matador triumphs
|
||||
|
||||
var _game_over: bool = false
|
||||
var _result_win: bool = false
|
||||
var _over_root: Control
|
||||
var _over_video: VideoStreamPlayer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -52,22 +54,35 @@ func _ready() -> void:
|
||||
_build_controls_panel()
|
||||
_build_tab_hint()
|
||||
_build_ability_bar()
|
||||
_build_health_bar()
|
||||
_build_scoreboard()
|
||||
_build_version_label()
|
||||
_build_game_over()
|
||||
_find_spawner.call_deferred()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_combo(delta)
|
||||
# Unobtrusive early-build tag in the bottom-right corner.
|
||||
func _build_version_label() -> void:
|
||||
var lbl := Label.new()
|
||||
lbl.text = GameVersion.full()
|
||||
lbl.add_theme_color_override("font_color", _MUTED)
|
||||
lbl.add_theme_font_size_override("font_size", 12)
|
||||
lbl.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
lbl.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
lbl.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
lbl.offset_right = -14.0
|
||||
lbl.offset_bottom = -10.0
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(lbl)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _player == null:
|
||||
var players := get_tree().get_nodes_in_group(&"player")
|
||||
if not players.is_empty():
|
||||
_player = players[0]
|
||||
return
|
||||
if not _health_connected:
|
||||
_health_connected = true
|
||||
_player.health_changed.connect(_on_health_changed)
|
||||
_on_health_changed(_player.health)
|
||||
if not _died_connected:
|
||||
_died_connected = true
|
||||
_player.died.connect(_on_player_died)
|
||||
for i: int in _cd_overlays.size():
|
||||
var fraction: float = _player.call(&"ability_cooldown_fraction", i)
|
||||
var ov := _cd_overlays[i]
|
||||
@@ -87,14 +102,14 @@ func _find_spawner() -> void:
|
||||
if spawners.is_empty():
|
||||
return
|
||||
_spawner = spawners[0]
|
||||
_spawner.matador_killed.connect(_on_matador_killed)
|
||||
_spawner.wave_changed.connect(_on_wave_changed)
|
||||
_on_wave_changed(_spawner.current_wave())
|
||||
_spawner.all_defeated.connect(_on_all_defeated)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.physical_keycode == KEY_TAB:
|
||||
if _game_over and event.physical_keycode in [KEY_ENTER, KEY_KP_ENTER]:
|
||||
_restart()
|
||||
elif event.physical_keycode == KEY_TAB and not _game_over:
|
||||
_toggle_controls()
|
||||
elif event.physical_keycode == KEY_ESCAPE:
|
||||
_return_to_menu()
|
||||
@@ -106,6 +121,12 @@ func _return_to_menu() -> void:
|
||||
get_tree().change_scene_to_file("res://MainMenu.tscn")
|
||||
|
||||
|
||||
func _restart() -> void:
|
||||
get_tree().paused = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
|
||||
func _toggle_controls() -> void:
|
||||
_controls_visible = not _controls_visible
|
||||
_controls_panel.visible = _controls_visible
|
||||
@@ -113,215 +134,111 @@ func _toggle_controls() -> void:
|
||||
_toggle_btn.text = "Hide controls" if _controls_visible else "Show controls"
|
||||
|
||||
|
||||
func _on_reset_pressed() -> void:
|
||||
if get_tree().paused:
|
||||
get_tree().paused = false
|
||||
if _spawner:
|
||||
_spawner.reset_spawn()
|
||||
_score = 0
|
||||
_score_label.text = "SCORE 0"
|
||||
_reset_combo()
|
||||
# ── Win / lose ──────────────────────────────────────────────────────────────
|
||||
# The bull takes one clean blade and it's over; clear the last matador and it's a
|
||||
# victory. Both routes end here — the arena freezes and the result is proclaimed.
|
||||
|
||||
func _on_player_died(cause: String = "") -> void:
|
||||
_show_game_over(false, cause)
|
||||
|
||||
|
||||
# A bordered, rounded, gold-trimmed panel background — the HUD's one repeated look.
|
||||
# Margins are symmetric (L=R, T=B); pass a transparent border to draw none.
|
||||
func _panel_style(bg: Color, corner: int, h_margin: float, v_margin: float,
|
||||
border: Color = _BORDER) -> StyleBoxFlat:
|
||||
var s := StyleBoxFlat.new()
|
||||
s.bg_color = bg
|
||||
s.set_corner_radius_all(corner)
|
||||
if border.a > 0.0:
|
||||
s.set_border_width_all(1)
|
||||
s.border_color = border
|
||||
s.content_margin_left = h_margin
|
||||
s.content_margin_right = h_margin
|
||||
s.content_margin_top = v_margin
|
||||
s.content_margin_bottom = v_margin
|
||||
return s
|
||||
func _on_all_defeated() -> void:
|
||||
_show_game_over(true)
|
||||
|
||||
|
||||
func _build_health_bar() -> void:
|
||||
var bar := HBoxContainer.new()
|
||||
bar.add_theme_constant_override("separation", 4)
|
||||
bar.anchor_left = 0.5
|
||||
bar.anchor_right = 0.5
|
||||
bar.anchor_top = 0.0
|
||||
bar.anchor_bottom = 0.0
|
||||
bar.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
bar.offset_left = -90.0
|
||||
bar.offset_top = 16.0
|
||||
bar.offset_bottom = 16.0
|
||||
add_child(bar)
|
||||
|
||||
var panel := PanelContainer.new()
|
||||
panel.add_theme_stylebox_override("panel", _panel_style(_BG, 4, 6.0, 6.0))
|
||||
bar.add_child(panel)
|
||||
|
||||
var inner := HBoxContainer.new()
|
||||
inner.add_theme_constant_override("separation", 3)
|
||||
panel.add_child(inner)
|
||||
|
||||
for i: int in 10:
|
||||
var seg := ColorRect.new()
|
||||
seg.custom_minimum_size = Vector2(14.0, 14.0)
|
||||
seg.color = Color(0.72, 0.08, 0.08)
|
||||
inner.add_child(seg)
|
||||
_health_segments.append(seg)
|
||||
|
||||
|
||||
func _on_health_changed(new_health: int) -> void:
|
||||
for i: int in _health_segments.size():
|
||||
_health_segments[i].color = Color(0.72, 0.08, 0.08) if i < new_health \
|
||||
else Color(0.20, 0.10, 0.10)
|
||||
|
||||
|
||||
# ── Scoreboard: score · wave · kill-combo ──────────────────────────────────────
|
||||
|
||||
func _build_scoreboard() -> void:
|
||||
var panel := PanelContainer.new()
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
panel.anchor_left = 1.0
|
||||
panel.anchor_right = 1.0
|
||||
panel.anchor_top = 0.0
|
||||
panel.anchor_bottom = 0.0
|
||||
panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
panel.offset_left = -172.0
|
||||
panel.offset_right = -14.0
|
||||
panel.offset_top = 14.0
|
||||
panel.add_theme_stylebox_override("panel", _panel_style(_BG, 6, 12.0, 8.0))
|
||||
add_child(panel)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vbox.add_theme_constant_override("separation", 2)
|
||||
panel.add_child(vbox)
|
||||
|
||||
_score_label = _score_line(vbox, "SCORE 0", _GOLD, 20)
|
||||
_wave_label = _score_line(vbox, "WAVE 1", _CREAM, 14)
|
||||
_combo_label = _score_line(vbox, "", _CREAM, 16)
|
||||
|
||||
_combo_track = ColorRect.new()
|
||||
_combo_track.color = Color(0.0, 0.0, 0.0, 0.4)
|
||||
_combo_track.custom_minimum_size = Vector2(0.0, 4.0)
|
||||
_combo_track.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_combo_track.visible = false
|
||||
vbox.add_child(_combo_track)
|
||||
|
||||
_combo_fill = ColorRect.new()
|
||||
_combo_fill.color = _GOLD
|
||||
_combo_fill.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_combo_fill.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_combo_track.add_child(_combo_fill)
|
||||
|
||||
# Centre banner that flashes on each new wave.
|
||||
_banner_label = Label.new()
|
||||
_banner_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_banner_label.anchor_left = 0.5
|
||||
_banner_label.anchor_right = 0.5
|
||||
_banner_label.anchor_top = 0.26
|
||||
_banner_label.anchor_bottom = 0.26
|
||||
_banner_label.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
_banner_label.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||
_banner_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_banner_label.add_theme_color_override("font_color", _GOLD)
|
||||
_banner_label.add_theme_font_size_override("font_size", 44)
|
||||
_banner_label.modulate.a = 0.0
|
||||
add_child(_banner_label)
|
||||
|
||||
|
||||
func _score_line(parent: VBoxContainer, text: String, color: Color, size: int) -> Label:
|
||||
var lbl := Label.new()
|
||||
lbl.text = text
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
lbl.add_theme_color_override("font_color", color)
|
||||
lbl.add_theme_font_size_override("font_size", size)
|
||||
parent.add_child(lbl)
|
||||
return lbl
|
||||
|
||||
|
||||
# Every kill lengthens the combo; scoring is base × combo so a fast chain is worth
|
||||
# far more than the same kills spread out — rewarding aggressive, uninterrupted play.
|
||||
func _on_matador_killed() -> void:
|
||||
_combo += 1
|
||||
_combo_timer = _COMBO_WINDOW
|
||||
_score += _KILL_BASE * _combo
|
||||
_score_label.text = "SCORE %d" % _score
|
||||
_pulse(_score_label, 1.15)
|
||||
if _combo >= 2:
|
||||
_combo_label.text = "%d× COMBO" % _combo
|
||||
_combo_label.add_theme_color_override("font_color", _combo_color())
|
||||
_combo_fill.color = _combo_color()
|
||||
_combo_track.visible = true
|
||||
_pulse(_combo_label, 1.25)
|
||||
else:
|
||||
_combo_label.text = ""
|
||||
_combo_track.visible = false
|
||||
|
||||
|
||||
func _on_wave_changed(wave: int) -> void:
|
||||
_wave_label.text = "WAVE %d" % wave
|
||||
if wave > 1:
|
||||
_flash_banner("WAVE %d" % wave)
|
||||
|
||||
|
||||
func _update_combo(delta: float) -> void:
|
||||
if _combo <= 0:
|
||||
func _show_game_over(win: bool, _cause: String = "") -> void:
|
||||
if _game_over:
|
||||
return
|
||||
_combo_timer -= delta
|
||||
if _combo_timer <= 0.0:
|
||||
_reset_combo()
|
||||
elif _combo_track.visible:
|
||||
_combo_fill.anchor_right = clampf(_combo_timer / _COMBO_WINDOW, 0.0, 1.0)
|
||||
_game_over = true
|
||||
_result_win = win
|
||||
|
||||
if _controls_panel:
|
||||
_controls_panel.visible = false
|
||||
if _tab_hint:
|
||||
_tab_hint.visible = false
|
||||
|
||||
# Swap in the matching outcome clip and loop it behind the buttons.
|
||||
var path := _WIN_VIDEO if win else _LOSS_VIDEO
|
||||
if ResourceLoader.exists(path):
|
||||
_over_video.stream = load(path)
|
||||
_over_video.play()
|
||||
|
||||
_over_root.visible = true
|
||||
_over_root.modulate.a = 0.0
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
get_tree().paused = true
|
||||
|
||||
create_tween().tween_property(_over_root, "modulate:a", 1.0, 0.5)
|
||||
|
||||
|
||||
func _reset_combo() -> void:
|
||||
_combo = 0
|
||||
_combo_timer = 0.0
|
||||
_combo_label.text = ""
|
||||
_combo_track.visible = false
|
||||
func _build_game_over() -> void:
|
||||
_over_root = Control.new()
|
||||
_over_root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_over_root.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_over_root.visible = false
|
||||
add_child(_over_root)
|
||||
|
||||
# Full-screen outcome clip (bull win / matador win), looping.
|
||||
_over_video = VideoStreamPlayer.new()
|
||||
_over_video.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_over_video.expand = true
|
||||
_over_video.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_over_video.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_over_video.finished.connect(_over_video.play) # loop the outcome clip
|
||||
_over_root.add_child(_over_video)
|
||||
|
||||
# Buttons overlaid at the bottom of the clip.
|
||||
var row := HBoxContainer.new()
|
||||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
row.add_theme_constant_override("separation", 16)
|
||||
row.anchor_left = 0.5
|
||||
row.anchor_right = 0.5
|
||||
row.anchor_top = 1.0
|
||||
row.anchor_bottom = 1.0
|
||||
row.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
row.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
row.offset_bottom = -48.0
|
||||
_over_root.add_child(row)
|
||||
|
||||
var again := _make_button("Go Again (Enter)")
|
||||
again.pressed.connect(_restart)
|
||||
row.add_child(again)
|
||||
|
||||
var menu := _make_button("Main Menu")
|
||||
menu.pressed.connect(_return_to_menu)
|
||||
row.add_child(menu)
|
||||
|
||||
|
||||
func _combo_color() -> Color:
|
||||
if _combo >= 8:
|
||||
return Color(1.0, 0.25, 0.10)
|
||||
if _combo >= 5:
|
||||
return Color(1.0, 0.50, 0.10)
|
||||
if _combo >= 3:
|
||||
return Color(1.0, 0.85, 0.20)
|
||||
return _CREAM
|
||||
|
||||
|
||||
func _pulse(ctrl: Control, amount: float) -> void:
|
||||
ctrl.pivot_offset = ctrl.size * 0.5
|
||||
ctrl.scale = Vector2.ONE * amount
|
||||
create_tween().tween_property(ctrl, "scale", Vector2.ONE, 0.25) \
|
||||
.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
|
||||
|
||||
func _flash_banner(text: String) -> void:
|
||||
_banner_label.text = text
|
||||
_banner_label.modulate.a = 0.0
|
||||
var tw := create_tween()
|
||||
tw.tween_property(_banner_label, "modulate:a", 1.0, 0.2)
|
||||
tw.tween_interval(0.7)
|
||||
tw.tween_property(_banner_label, "modulate:a", 0.0, 0.6)
|
||||
const _SLOT_SEPARATION: int = 10
|
||||
|
||||
|
||||
func _build_ability_bar() -> void:
|
||||
var bar := HBoxContainer.new()
|
||||
bar.add_theme_constant_override("separation", 10)
|
||||
bar.anchor_left = 0.5
|
||||
bar.anchor_right = 0.5
|
||||
bar.anchor_top = 1.0
|
||||
bar.anchor_bottom = 1.0
|
||||
bar.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
bar.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
bar.offset_left = -160.0
|
||||
bar.offset_bottom = -20.0
|
||||
add_child(bar)
|
||||
_ability_bar = HBoxContainer.new()
|
||||
_ability_bar.add_theme_constant_override("separation", _SLOT_SEPARATION)
|
||||
_ability_bar.anchor_left = 0.5
|
||||
_ability_bar.anchor_right = 0.5
|
||||
_ability_bar.anchor_top = 1.0
|
||||
_ability_bar.anchor_bottom = 1.0
|
||||
_ability_bar.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
_ability_bar.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
_ability_bar.offset_bottom = -20.0
|
||||
add_child(_ability_bar)
|
||||
for i: int in 4:
|
||||
bar.add_child(_build_ability_slot(i))
|
||||
_ability_bar.add_child(_build_ability_slot(i))
|
||||
_layout_ability_bar()
|
||||
get_viewport().size_changed.connect(_layout_ability_bar)
|
||||
|
||||
|
||||
# Size the ability slots as a fraction of the viewport (clamped) so the bar scales
|
||||
# with the window, and keep it horizontally centred by matching the HBox offsets.
|
||||
func _layout_ability_bar() -> void:
|
||||
var vp := get_viewport().get_visible_rect().size
|
||||
var slot := clampf(minf(vp.x * 0.055, vp.y * 0.11), 56.0, 104.0)
|
||||
for panel: PanelContainer in _ability_panels:
|
||||
panel.custom_minimum_size = Vector2(slot, slot)
|
||||
var total := slot * 4.0 + float(_SLOT_SEPARATION) * 3.0
|
||||
_ability_bar.offset_left = -total * 0.5
|
||||
_ability_bar.offset_right = total * 0.5
|
||||
|
||||
|
||||
func _build_ability_slot(idx: int) -> Control:
|
||||
@@ -330,8 +247,10 @@ func _build_ability_slot(idx: int) -> Control:
|
||||
|
||||
var panel := PanelContainer.new()
|
||||
panel.custom_minimum_size = Vector2(72.0, 72.0)
|
||||
panel.add_theme_stylebox_override("panel", _panel_style(_BG, 6, 4.0, 4.0))
|
||||
panel.add_theme_stylebox_override(
|
||||
"panel", UiTheme.plaque(UiTheme.LEATHER, _ABILITY_ACCENT[idx], 4, 5.0, 5.0))
|
||||
vbox.add_child(panel)
|
||||
_ability_panels.append(panel)
|
||||
|
||||
var stack := Control.new()
|
||||
stack.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -340,8 +259,12 @@ func _build_ability_slot(idx: int) -> Control:
|
||||
if ResourceLoader.exists(_ABILITY_ICONS[idx]):
|
||||
var icon := TextureRect.new()
|
||||
icon.texture = load(_ABILITY_ICONS[idx])
|
||||
# Ignore the texture's native size so a large source PNG doesn't blow the
|
||||
# slot's minimum size out; scale-to-fit the box while keeping aspect.
|
||||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
stack.add_child(icon)
|
||||
else:
|
||||
var name_lbl := Label.new()
|
||||
@@ -378,8 +301,11 @@ func _build_ability_slot(idx: int) -> Control:
|
||||
var key_lbl := Label.new()
|
||||
key_lbl.text = Controls.get_key_label(_ABILITY_ACTIONS[idx])
|
||||
key_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
key_lbl.add_theme_color_override("font_color", _GOLD)
|
||||
key_lbl.add_theme_font_size_override("font_size", 12)
|
||||
key_lbl.add_theme_font_override("font", UiFonts.body())
|
||||
key_lbl.add_theme_color_override("font_color", _ABILITY_ACCENT[idx].lerp(_CREAM, 0.35))
|
||||
key_lbl.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.85))
|
||||
key_lbl.add_theme_constant_override("outline_size", 3)
|
||||
key_lbl.add_theme_font_size_override("font_size", 13)
|
||||
vbox.add_child(key_lbl)
|
||||
|
||||
return vbox
|
||||
@@ -402,7 +328,8 @@ func _build_tab_hint() -> void:
|
||||
|
||||
func _build_controls_panel() -> void:
|
||||
_controls_panel = PanelContainer.new()
|
||||
_controls_panel.add_theme_stylebox_override("panel", _panel_style(_BG, 7, 14.0, 10.0))
|
||||
_controls_panel.add_theme_stylebox_override(
|
||||
"panel", UiTheme.plaque(UiTheme.LEATHER, UiTheme.BORDER, 4, 16.0, 12.0))
|
||||
|
||||
_controls_panel.anchor_left = 0.0
|
||||
_controls_panel.anchor_top = 1.0
|
||||
@@ -424,37 +351,22 @@ func _build_controls_panel() -> void:
|
||||
Controls.get_key_label(&"move_back"),
|
||||
Controls.get_key_label(&"move_right"),
|
||||
]
|
||||
_row(vbox, move_keys, "Move (not intended)", _MUTED)
|
||||
_row(vbox, "LMB (hold)", "Right horn", _CREAM)
|
||||
_row(vbox, "RMB (hold)", "Left horn", _CREAM)
|
||||
_row(vbox, move_keys, "Move", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_kick"), "Kick", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_slam"), "Slam", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_dash"), "Dash", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_roll"), "Roll (bank off walls!)", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_roll"), "Roll (Powerball: ram the matador!)", _CREAM)
|
||||
_row(vbox, "Scroll", "Zoom", _CREAM)
|
||||
_row(vbox, "R", "Respawn matadors (cooldown)", _MUTED)
|
||||
_row(vbox, "Tab", "Toggle controls", _MUTED)
|
||||
_row(vbox, "Esc", "Back to menu", _MUTED)
|
||||
|
||||
var sep := HSeparator.new()
|
||||
var sep_style := StyleBoxFlat.new()
|
||||
sep_style.bg_color = Color(0.65, 0.48, 0.15, 0.30)
|
||||
sep_style.content_margin_top = 4.0
|
||||
sep_style.content_margin_bottom = 4.0
|
||||
sep.add_theme_stylebox_override("separator", sep_style)
|
||||
sep.add_theme_stylebox_override("separator", UiTheme.rule())
|
||||
vbox.add_child(sep)
|
||||
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_theme_constant_override("separation", 8)
|
||||
vbox.add_child(hbox)
|
||||
|
||||
_toggle_btn = _make_button("Show controls")
|
||||
_toggle_btn.pressed.connect(_toggle_controls)
|
||||
hbox.add_child(_toggle_btn)
|
||||
|
||||
var reset_btn := _make_button("Reset")
|
||||
reset_btn.pressed.connect(_on_reset_pressed)
|
||||
hbox.add_child(reset_btn)
|
||||
vbox.add_child(_toggle_btn)
|
||||
|
||||
|
||||
func _make_button(label_text: String) -> Button:
|
||||
@@ -462,15 +374,17 @@ func _make_button(label_text: String) -> Button:
|
||||
btn.text = label_text
|
||||
btn.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
var normal := _panel_style(Color(0.18, 0.12, 0.04, 0.90), 5, 12.0, 6.0)
|
||||
var hover := _panel_style(Color(0.30, 0.20, 0.06, 0.95), 5, 12.0, 6.0, _GOLD)
|
||||
var normal := UiTheme.plaque(Color(0.10, 0.07, 0.03, 0.90), _GOLD, 3, 18.0, 9.0)
|
||||
var hover := UiTheme.plaque(Color(0.16, 0.10, 0.04, 0.96), _CREAM, 3, 18.0, 9.0)
|
||||
|
||||
btn.add_theme_stylebox_override("normal", normal)
|
||||
btn.add_theme_stylebox_override("hover", hover)
|
||||
btn.add_theme_stylebox_override("pressed", hover)
|
||||
btn.add_theme_stylebox_override("focus", hover)
|
||||
btn.add_theme_font_override("font", UiFonts.body())
|
||||
btn.add_theme_color_override("font_color", _GOLD)
|
||||
btn.add_theme_color_override("font_hover_color", _GOLD)
|
||||
btn.add_theme_font_size_override("font_size", 12)
|
||||
btn.add_theme_color_override("font_hover_color", _CREAM)
|
||||
btn.add_theme_font_size_override("font_size", 18)
|
||||
|
||||
return btn
|
||||
|
||||
@@ -481,7 +395,7 @@ func _row(parent: VBoxContainer, key: String, desc: String, desc_color: Color) -
|
||||
parent.add_child(row)
|
||||
|
||||
var badge := PanelContainer.new()
|
||||
badge.add_theme_stylebox_override("panel", _panel_style(_BADGE, 3, 6.0, 2.0, Color(0, 0, 0, 0)))
|
||||
badge.add_theme_stylebox_override("panel", UiTheme.badge())
|
||||
badge.custom_minimum_size = Vector2(100.0, 0.0)
|
||||
row.add_child(badge)
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ extends Control
|
||||
const GAME_SCENE := "res://scene.tscn"
|
||||
const FADE_TIME := 0.6
|
||||
|
||||
# Arena palette: aged gold on dark leather, warm cream on hover.
|
||||
const MENU_GOLD := Color(0.85, 0.66, 0.20)
|
||||
const MENU_CREAM := Color(0.93, 0.88, 0.72)
|
||||
|
||||
## Cape centroid per frame (normalized 0-1), detected from red-pixel centroid.
|
||||
## 34 frames at 24 fps = 1.4167 s loop.
|
||||
const VIDEO_FPS := 24.0
|
||||
@@ -90,10 +94,69 @@ func _ready() -> void:
|
||||
exit_button.pressed.connect(_on_exit_pressed)
|
||||
options_back.pressed.connect(_close_subpanels)
|
||||
credits_back.pressed.connect(_close_subpanels)
|
||||
_apply_theme()
|
||||
_build_version_label()
|
||||
_build_rebind_rows()
|
||||
play_button.grab_focus()
|
||||
|
||||
|
||||
# Version banner in the bottom-right corner so it's clear this is an early build.
|
||||
func _build_version_label() -> void:
|
||||
var lbl := Label.new()
|
||||
lbl.text = GameVersion.full()
|
||||
lbl.add_theme_font_override("font", UiFonts.body())
|
||||
lbl.add_theme_font_size_override("font_size", 16)
|
||||
lbl.add_theme_color_override("font_color", MENU_CREAM)
|
||||
lbl.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
lbl.add_theme_constant_override("outline_size", 4)
|
||||
lbl.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
lbl.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
lbl.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
lbl.offset_right = -16.0
|
||||
lbl.offset_bottom = -12.0
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(lbl)
|
||||
|
||||
|
||||
# Give the whole menu the Cinzel look and swap the generic translucent boxes for
|
||||
# engraved bronze plaques (dark leather fill, gold border with a heavier bottom
|
||||
# edge for a chiselled-tablet feel).
|
||||
func _apply_theme() -> void:
|
||||
_style_button(play_button, true)
|
||||
for btn: Button in [options_button, credits_button, exit_button, options_back, credits_back]:
|
||||
_style_button(btn, false)
|
||||
for path: String in ["OptionsPanel/VBox/TitleLabel", "CreditsPanel/VBox/TitleLabel"]:
|
||||
var title := get_node_or_null(path) as Label
|
||||
if title != null:
|
||||
title.add_theme_font_override("font", UiFonts.title())
|
||||
title.add_theme_color_override("font_color", MENU_GOLD)
|
||||
for path: String in ["OptionsPanel/VBox/Hint", "CreditsPanel/VBox/Names"]:
|
||||
var lbl := get_node_or_null(path) as Label
|
||||
if lbl != null:
|
||||
lbl.add_theme_font_override("font", UiFonts.body())
|
||||
lbl.add_theme_color_override("font_color", MENU_CREAM)
|
||||
|
||||
|
||||
func _style_button(btn: Button, big: bool) -> void:
|
||||
btn.add_theme_font_override("font", UiFonts.title() if big else UiFonts.body())
|
||||
btn.add_theme_color_override("font_color", MENU_GOLD)
|
||||
btn.add_theme_color_override("font_hover_color", MENU_CREAM)
|
||||
btn.add_theme_color_override("font_focus_color", MENU_CREAM)
|
||||
btn.add_theme_color_override("font_pressed_color", MENU_CREAM)
|
||||
btn.add_theme_color_override("font_outline_color", Color(0, 0, 0, 1))
|
||||
btn.add_theme_constant_override("outline_size", 8 if big else 5)
|
||||
btn.add_theme_stylebox_override("normal", _plaque(false))
|
||||
btn.add_theme_stylebox_override("hover", _plaque(true))
|
||||
btn.add_theme_stylebox_override("pressed", _plaque(true))
|
||||
btn.add_theme_stylebox_override("focus", _plaque(true))
|
||||
|
||||
|
||||
func _plaque(hot: bool) -> StyleBoxFlat:
|
||||
var bg := Color(0.14, 0.09, 0.04, 0.94) if hot else Color(0.07, 0.05, 0.03, 0.82)
|
||||
var trim := MENU_CREAM if hot else MENU_GOLD
|
||||
return UiTheme.plaque(bg, trim, 2, 24.0, 9.0)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not main_panel.visible or video.stream == null:
|
||||
return
|
||||
@@ -117,6 +180,9 @@ func _build_rebind_rows() -> void:
|
||||
var name_label := Label.new()
|
||||
name_label.text = ACTION_LABELS[action]
|
||||
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
name_label.add_theme_font_override("font", UiFonts.body())
|
||||
name_label.add_theme_color_override("font_color", MENU_CREAM)
|
||||
name_label.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
name_label.add_theme_constant_override("outline_size", 4)
|
||||
row.add_child(name_label)
|
||||
@@ -124,6 +190,7 @@ func _build_rebind_rows() -> void:
|
||||
var key_button := Button.new()
|
||||
key_button.custom_minimum_size = Vector2(160, 0)
|
||||
key_button.text = Controls.get_key_label(action)
|
||||
_style_button(key_button, false)
|
||||
key_button.pressed.connect(_on_rebind_pressed.bind(action, key_button))
|
||||
row.add_child(key_button)
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ extends CharacterBody3D
|
||||
# RAGDOLL → hit by bull, no roll chance
|
||||
enum State { WANDER, FLEE, BRACE, SIDESTEP, ATTACK, ROLL, THROW, RAGDOLL }
|
||||
|
||||
# The matador's high-level *want*, chosen by a weighted utility vote (see
|
||||
# _choose_intent). Each intent maps onto one or more concrete States below.
|
||||
enum Intent { WANDER, FLEE, ATTACK, DODGE, THROW }
|
||||
|
||||
signal killed
|
||||
|
||||
var _state: State = State.WANDER
|
||||
@@ -35,6 +39,8 @@ var _throw_aim_cd: float = 0.0
|
||||
var _throw_dir: Vector3 = Vector3.ZERO
|
||||
var _steer_dir: Vector3 = Vector3.ZERO
|
||||
var _steer_cd: float = 0.0
|
||||
var _intent: Intent = Intent.WANDER
|
||||
var _decide_cd: float = 0.0
|
||||
var _ole_player: AudioStreamPlayer = null
|
||||
var _sword_hand_attach: BoneAttachment3D = null
|
||||
var _sword_rest_attach: BoneAttachment3D = null
|
||||
@@ -74,11 +80,12 @@ const _STEER_INTERVAL: float = 0.12 # recompute direction at most ~8×/sec
|
||||
# Side angles (radians) tried when the straight-ahead path is blocked.
|
||||
const _STEER_CANDIDATES: Array = [-0.35, 0.35, -0.7, 0.7, -1.1, 1.1, PI]
|
||||
|
||||
# Movement tuning
|
||||
const _ACCEL_FACTOR: float = 8.0 # velocity ramp = speed * factor * delta
|
||||
const _TURN_MOVE: float = 8.0 # face the travel direction while roaming
|
||||
const _TURN_FACE: float = 12.0 # face the bull while engaging
|
||||
const _TURN_SHARP: float = 20.0 # snap onto a sidestep / roll direction
|
||||
# Movement tuning. Ramp/turn rates are deliberately snappy so the matador reads as
|
||||
# a quick, dangerous opponent rather than a sluggish one.
|
||||
const _ACCEL_FACTOR: float = 13.0 # velocity ramp = speed * factor * delta
|
||||
const _TURN_MOVE: float = 11.0 # face the travel direction while roaming
|
||||
const _TURN_FACE: float = 16.0 # face the bull while engaging
|
||||
const _TURN_SHARP: float = 24.0 # snap onto a sidestep / roll direction
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -97,7 +104,7 @@ func _ready() -> void:
|
||||
_anim_player.play(_ANIM_IDLE)
|
||||
_setup_sword()
|
||||
DP.any_changed.connect(_on_dp_changed)
|
||||
_throw_aim_cd = randf_range(0.8, 2.5)
|
||||
_throw_aim_cd = randf_range(0.3, 1.0)
|
||||
_hit_area.body_entered.connect(_on_body_entered)
|
||||
_pick_wander_target()
|
||||
_blood_burst = preload("res://blood_burst.gd").new()
|
||||
@@ -148,7 +155,7 @@ func _physics_process(delta: float) -> void:
|
||||
if _resword_timer <= 0.0:
|
||||
_spawn_sword()
|
||||
if _state != State.RAGDOLL and _bull != null:
|
||||
_update_ai_state()
|
||||
_update_ai_state(delta)
|
||||
|
||||
match _state:
|
||||
State.WANDER: _tick_wander(delta)
|
||||
@@ -164,42 +171,110 @@ func _physics_process(delta: float) -> void:
|
||||
_advance_draw(delta)
|
||||
|
||||
|
||||
func _update_ai_state() -> void:
|
||||
if _state == State.THROW:
|
||||
# Utility-driven brain. The physically committed states (a brace, a pass, a roll, a
|
||||
# throw wind-up) run to completion on their own timers; from the free states the
|
||||
# matador re-scores its options every mat_decide_interval and acts on the winner.
|
||||
func _update_ai_state(delta: float) -> void:
|
||||
if _state == State.BRACE or _state == State.SIDESTEP \
|
||||
or _state == State.ROLL or _state == State.THROW:
|
||||
return
|
||||
|
||||
_decide_cd -= delta
|
||||
if _decide_cd > 0.0:
|
||||
return
|
||||
_decide_cd = DP.f("mat_decide_interval")
|
||||
|
||||
var intent := _choose_intent()
|
||||
_intent = intent
|
||||
if not _realizes_intent(intent):
|
||||
_enter_intent(intent)
|
||||
|
||||
|
||||
# Score each intent from weighted heuristics and return the winner. Weights (the
|
||||
# mat_w_* params) shape the personality; a small commit bonus on the current intent
|
||||
# stops it flip-flopping between two near-tied options.
|
||||
func _choose_intent() -> Intent:
|
||||
var dist := global_position.distance_to(_bull.global_position)
|
||||
if (_state == State.WANDER or _state == State.FLEE or _state == State.ATTACK) \
|
||||
and _throw_aim_cd <= 0.0 and is_instance_valid(_sword_node) \
|
||||
and dist >= DP.f("mat_throw_min_dist") and dist <= DP.f("mat_throw_range"):
|
||||
# Roll for a throw; on a miss, wait before becoming eligible again so the
|
||||
# matador commits to chasing for a melee Attack instead of throwing early.
|
||||
if randf() < DP.f("mat_throw_chance"):
|
||||
var bull_flat := Vector3(_bull.velocity.x, 0.0, _bull.velocity.z)
|
||||
var bull_speed := bull_flat.length()
|
||||
|
||||
# close = 1 right on top of us, 0 at attack range or beyond. `beyond` measures
|
||||
# how far past throw range the bull is (0 point-blank, 1 at/outside throw range):
|
||||
# the matador only idles when the bull is out there, and engages otherwise.
|
||||
var reach := maxf(DP.f("mat_attack_range"), 0.5)
|
||||
var throw_range := maxf(DP.f("mat_throw_range"), 1.0)
|
||||
var close := clampf(1.0 - dist / reach, 0.0, 1.0)
|
||||
var beyond := clampf(dist / throw_range, 0.0, 1.0)
|
||||
# danger blends "the bull is fast and near" with "a charge is aimed at me".
|
||||
var danger := clampf(bull_speed / maxf(DP.f("mat_charge_speed"), 0.1), 0.0, 1.0) * close
|
||||
if _is_charge_incoming():
|
||||
danger = clampf(danger + 0.7, 0.0, 1.0)
|
||||
|
||||
# A matador won't commit to a strike it can't bail out of, so a spent dodge
|
||||
# cooldown makes attacking (and dodging outright) less attractive.
|
||||
var dodge_ready := 1.0 if _dodge_cd <= 0.0 else 0.35
|
||||
var throw_ok := _throw_aim_cd <= 0.0 and is_instance_valid(_sword_node) \
|
||||
and dist >= DP.f("mat_throw_min_dist") and dist <= DP.f("mat_throw_range")
|
||||
|
||||
var score := {
|
||||
# Only drift when the bull is out past throw range; up close this is ~0.
|
||||
Intent.WANDER: DP.f("mat_w_showmanship") * 0.3 * beyond,
|
||||
# Retreat only when the bull is close AND actually dangerous.
|
||||
Intent.FLEE: DP.f("mat_w_flee") * close * (0.25 + 0.75 * danger),
|
||||
# Press the attack; ease off a touch when a charge is barrelling in.
|
||||
Intent.ATTACK: DP.f("mat_w_aggression") * (0.45 + 0.55 * close) \
|
||||
* (1.0 - 0.55 * danger) * dodge_ready,
|
||||
# Sidestep / plant against an incoming charge.
|
||||
Intent.DODGE: DP.f("mat_w_caution") * danger * (1.0 if _dodge_cd <= 0.0 else 0.0),
|
||||
# Hurl the blade whenever there's a clean lane at range — the ranged threat
|
||||
# that punishes a bull for hanging back out of horn reach.
|
||||
Intent.THROW: ((DP.f("mat_w_showmanship") * 0.55 + DP.f("mat_w_aggression") * 0.6) \
|
||||
* (0.35 + 0.65 * (1.0 - close))) if throw_ok else -1.0,
|
||||
}
|
||||
score[_intent] = float(score[_intent]) + DP.f("mat_w_commit")
|
||||
|
||||
var best: Intent = Intent.WANDER
|
||||
var best_score := -INF
|
||||
for intent: int in score:
|
||||
if float(score[intent]) > best_score:
|
||||
best_score = float(score[intent])
|
||||
best = intent
|
||||
return best
|
||||
|
||||
|
||||
# True when the current State already carries out the intent, so we needn't restart
|
||||
# it (restarting ATTACK every tick would reset its swing and stutter the animation).
|
||||
func _realizes_intent(intent: Intent) -> bool:
|
||||
match intent:
|
||||
Intent.WANDER:
|
||||
return _state == State.WANDER
|
||||
Intent.FLEE:
|
||||
return _state == State.FLEE
|
||||
Intent.ATTACK:
|
||||
return _state == State.ATTACK
|
||||
Intent.DODGE:
|
||||
return _state == State.BRACE or _state == State.SIDESTEP
|
||||
Intent.THROW:
|
||||
return _state == State.THROW
|
||||
return false
|
||||
|
||||
|
||||
func _enter_intent(intent: Intent) -> void:
|
||||
match intent:
|
||||
Intent.WANDER:
|
||||
_state = State.WANDER
|
||||
_steer_cd = 0.0
|
||||
_pick_wander_target()
|
||||
Intent.FLEE:
|
||||
_state = State.FLEE
|
||||
_steer_cd = 0.0
|
||||
Intent.ATTACK:
|
||||
_steer_cd = 0.0
|
||||
_start_attack()
|
||||
Intent.DODGE:
|
||||
_start_brace()
|
||||
Intent.THROW:
|
||||
_start_throw()
|
||||
return
|
||||
_throw_aim_cd = randf_range(2.5, 5.0)
|
||||
match _state:
|
||||
State.WANDER:
|
||||
if dist < DP.f("mat_flee_range"):
|
||||
_state = State.FLEE
|
||||
elif _dodge_cd <= 0.0 and dist < DP.f("mat_attack_range") and not _is_charge_incoming():
|
||||
_steer_cd = 0.0
|
||||
_start_attack()
|
||||
State.FLEE:
|
||||
if _dodge_cd <= 0.0 and (dist < DP.f("mat_commit_dist") or _is_charge_incoming()):
|
||||
_start_brace()
|
||||
elif dist > DP.f("mat_flee_range") * 1.3:
|
||||
_state = State.WANDER
|
||||
_steer_cd = 0.0
|
||||
_pick_wander_target()
|
||||
State.BRACE:
|
||||
pass # brace timer drives transition
|
||||
State.SIDESTEP:
|
||||
pass # step timer drives transition
|
||||
State.ATTACK:
|
||||
if _attack_timer <= 0.0 or dist > DP.f("mat_attack_range") * 1.5:
|
||||
_state = State.FLEE
|
||||
State.ROLL:
|
||||
pass # roll timer drives transition
|
||||
|
||||
|
||||
# Bull is moving fast and aimed within ~30° of the matador
|
||||
@@ -339,16 +414,21 @@ func _tick_brace(delta: float) -> void:
|
||||
|
||||
_brace_timer -= delta
|
||||
var dist := global_position.distance_to(_bull.global_position)
|
||||
if dist < DP.f("mat_commit_dist") or _brace_timer <= 0.0:
|
||||
if _will_dodge:
|
||||
if _will_dodge:
|
||||
# Dodger: wait for the bull to commit, then whip into the lateral pass.
|
||||
if dist < DP.f("mat_commit_dist") or _brace_timer <= 0.0:
|
||||
_state = State.SIDESTEP
|
||||
_step_timer = DP.f("mat_step_duration")
|
||||
if _ole_player:
|
||||
_ole_player.pitch_scale = randf_range(0.88, 1.12)
|
||||
_ole_player.play()
|
||||
else:
|
||||
_state = State.FLEE
|
||||
else:
|
||||
# Planter (estocada): the bull only dies to an active, presented thrust, not to
|
||||
# brushing a braced matador — so when it commits in, break into a real attack
|
||||
# SWING (which lunges the blade forward and stabs), otherwise hold then press.
|
||||
if dist < DP.f("mat_commit_dist") or (_brace_timer <= 0.0 and not _is_charge_incoming()):
|
||||
_dodge_cd = DP.f("mat_dodge_cooldown")
|
||||
_start_attack()
|
||||
|
||||
|
||||
# Pase phase: sharp lateral step biased toward the bull so the drawn blade sweeps
|
||||
@@ -368,6 +448,7 @@ func _tick_sidestep(delta: float) -> void:
|
||||
velocity.z = dir.z * spd
|
||||
_face_point(_bull.global_position, delta, _TURN_SHARP)
|
||||
_play_anim(_ANIM_ATTACK)
|
||||
_try_melee_hit(DP.f("mat_stab_reach"))
|
||||
move_and_slide()
|
||||
|
||||
|
||||
@@ -406,6 +487,7 @@ func _tick_attack(delta: float) -> void:
|
||||
else:
|
||||
_decelerate(22.0, delta)
|
||||
_play_anim(_ANIM_ATTACK)
|
||||
_try_melee_hit(DP.f("mat_stab_reach"))
|
||||
else:
|
||||
var dir := _steer_clear(to_bull.normalized(), delta)
|
||||
_accelerate(dir, DP.f("mat_attack_speed"), delta)
|
||||
@@ -481,6 +563,10 @@ func _tick_throw(delta: float) -> void:
|
||||
func _end_throw() -> void:
|
||||
_state = State.FLEE
|
||||
_dodge_cd = DP.f("mat_dodge_cooldown")
|
||||
# Space throws out so the utility brain doesn't immediately vote another one,
|
||||
# but keep them frequent enough to pressure a bull that hangs back at range.
|
||||
_throw_aim_cd = randf_range(1.6, 3.2)
|
||||
_intent = Intent.FLEE
|
||||
# Blend straight into locomotion so the manually-posed throw arm eases out
|
||||
# (a bare resume() would snap and leave the player on whatever it paused on).
|
||||
if _anim_player:
|
||||
@@ -539,15 +625,20 @@ func _release_sword() -> void:
|
||||
rb.collision_layer = 0
|
||||
rb.collision_mask = 1
|
||||
rb.contact_monitor = true
|
||||
rb.max_contacts_reported = 4
|
||||
rb.max_contacts_reported = 6
|
||||
# Fly nearly straight to the aim point instead of lobbing — the old horizontal
|
||||
# throw sailed clean over the bull's low body. A small gravity_scale keeps a
|
||||
# touch of drop for feel without dropping short.
|
||||
rb.gravity_scale = DP.f("mat_throw_gravity")
|
||||
get_tree().current_scene.add_child(rb)
|
||||
rb.global_transform = gx
|
||||
sword.reparent(rb, true)
|
||||
|
||||
# Physics collider along the blade (GLB +Z), which equals rb-local +Z because
|
||||
# the mesh kept its global transform and rb adopted it.
|
||||
# the mesh kept its global transform and rb adopted it. Fattened so a fast throw
|
||||
# reliably overlaps the bull's collision spheres instead of tunnelling past.
|
||||
var cap := CapsuleShape3D.new()
|
||||
cap.radius = 0.06
|
||||
cap.radius = 0.14
|
||||
cap.height = 1.4
|
||||
var cs := CollisionShape3D.new()
|
||||
cs.shape = cap
|
||||
@@ -555,19 +646,25 @@ func _release_sword() -> void:
|
||||
cs.rotation_degrees = Vector3(90.0, 0.0, 0.0)
|
||||
rb.add_child(cs)
|
||||
|
||||
# Lead the target: aim where the bull will be when the sword arrives, so a
|
||||
# moving bull isn't simply behind the throw by the time it lands.
|
||||
var aim := _throw_dir
|
||||
# Aim at the bull's BODY (its collision spheres ride low, ~0.5 m below the
|
||||
# origin), leading a moving target so it arrives where the bull will be. The aim
|
||||
# keeps its vertical component so the blade drives into the body, not over it.
|
||||
var speed := maxf(DP.f("mat_throw_speed"), 0.1)
|
||||
var dir := (_throw_dir + Vector3.UP * 0.0)
|
||||
if is_instance_valid(_bull):
|
||||
var speed := maxf(DP.f("mat_throw_speed"), 0.1)
|
||||
var flat := _bull.global_position - gx.origin
|
||||
var target := _bull.global_position + Vector3(0.0, DP.f("mat_throw_aim_y"), 0.0)
|
||||
var flat := target - gx.origin
|
||||
flat.y = 0.0
|
||||
var lead := (_bull.global_position + _bull.velocity * (flat.length() / speed)) - gx.origin
|
||||
lead.y = 0.0
|
||||
if lead.length() > 0.1:
|
||||
aim = lead.normalized()
|
||||
var dir := (aim + Vector3.UP * 0.12).normalized()
|
||||
rb.linear_velocity = dir * DP.f("mat_throw_speed")
|
||||
var flight := flat.length() / speed
|
||||
target += _bull.velocity * flight
|
||||
var to_target := target - gx.origin
|
||||
if to_target.length() > 0.1:
|
||||
dir = to_target.normalized()
|
||||
# A little aim scatter so throws are a threat to READ and dodge, not a hitscan —
|
||||
# a bull that keeps moving can slip them, a stationary one gets pinned.
|
||||
var spread := deg_to_rad(DP.f("mat_throw_spread"))
|
||||
dir = dir.rotated(Vector3.UP, randf_range(-spread, spread)).normalized()
|
||||
rb.linear_velocity = dir * speed
|
||||
rb.angular_velocity = dir.cross(Vector3.UP).normalized() * -DP.f("mat_throw_spin")
|
||||
|
||||
var hit_done := [false]
|
||||
@@ -576,7 +673,7 @@ func _release_sword() -> void:
|
||||
return
|
||||
if body.is_in_group(&"player"):
|
||||
hit_done[0] = true
|
||||
body.call(&"take_sword_hit")
|
||||
body.call(&"take_sword_hit", "thrown")
|
||||
)
|
||||
|
||||
var cleanup := get_tree().create_timer(6.0)
|
||||
@@ -623,14 +720,36 @@ func _on_body_entered(body: Node3D) -> void:
|
||||
|
||||
|
||||
func _on_blade_hit(body: Node3D) -> void:
|
||||
if _state != State.ATTACK and _state != State.SIDESTEP:
|
||||
if _state != State.ATTACK and _state != State.SIDESTEP and _state != State.BRACE:
|
||||
return
|
||||
if not body.is_in_group(&"player"):
|
||||
return
|
||||
if _blade_hit_cd > 0.0:
|
||||
return
|
||||
_blade_hit_cd = 0.5
|
||||
body.call(&"take_sword_hit")
|
||||
_blade_hit_cd = DP.f("mat_stab_cooldown")
|
||||
body.call(&"take_sword_hit", "gored")
|
||||
|
||||
|
||||
# Reach-based stab. A charging bull crosses metres per physics frame, so it easily
|
||||
# tunnels through the thin blade Area between frames — this distance check is the
|
||||
# reliable hit. Called from the offensive ticks (active swing / pass) whenever the
|
||||
# blade is drawn: if the bull is inside reach AND the matador is facing it — the
|
||||
# blade actually presented, not held off to the side — it's gored.
|
||||
func _try_melee_hit(reach: float) -> void:
|
||||
if _blade_hit_cd > 0.0 or not _sword_in_hand or _bull == null:
|
||||
return
|
||||
var to_bull := _bull.global_position - global_position
|
||||
to_bull.y = 0.0
|
||||
var dist := to_bull.length()
|
||||
if dist > reach or dist < 0.01:
|
||||
return
|
||||
# Only lands within the frontal arc — you're gored on a presented blade, not by
|
||||
# brushing a matador whose sword is pointing elsewhere.
|
||||
var facing := Vector3(sin(_mesh.rotation.y), 0.0, cos(_mesh.rotation.y))
|
||||
if facing.dot(to_bull / dist) < cos(deg_to_rad(DP.f("mat_stab_arc"))):
|
||||
return
|
||||
_blade_hit_cd = DP.f("mat_stab_cooldown")
|
||||
_bull.call(&"take_sword_hit", "gored")
|
||||
|
||||
|
||||
func _enter_ragdoll(hit_dir: Vector3, bull_speed: float, up_boost: float = 0.0) -> void:
|
||||
@@ -833,8 +952,8 @@ func _spawn_sword() -> void:
|
||||
# Blade collider spans the steel (hilt at local origin, blade running +Z out
|
||||
# to ~1.4 m). Only monitors while the sword is in hand (see _carry_sword).
|
||||
var blade_cap := CapsuleShape3D.new()
|
||||
blade_cap.radius = 0.10
|
||||
blade_cap.height = 1.10
|
||||
blade_cap.radius = 0.18
|
||||
blade_cap.height = 1.30
|
||||
var blade_cs := CollisionShape3D.new()
|
||||
blade_cs.shape = blade_cap
|
||||
blade_cs.position = Vector3(0.0, 0.0, 0.8)
|
||||
|
||||
@@ -1,61 +1,53 @@
|
||||
extends Node3D
|
||||
|
||||
signal matador_killed
|
||||
signal wave_changed(wave: int)
|
||||
signal all_defeated
|
||||
|
||||
const MATADOR := preload("res://Matador.tscn")
|
||||
const RESPAWN_COOLDOWN := 8.0
|
||||
|
||||
var _respawn_cooldown: float = 0.0
|
||||
var _spawn_count: int = 1 # matadors in the current wave — doubles on each clear
|
||||
var _wave: int = 1 # wave number, surfaced on the HUD
|
||||
var _spawn_count: int = 1
|
||||
var _alive: int = 0
|
||||
var _resolved: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group(&"matador_spawn")
|
||||
_spawn_count = maxi(1, int(DP.f("mat_spawn_count")))
|
||||
_start_wave(1)
|
||||
|
||||
|
||||
# Latest wave number, for a HUD that connects after the first wave has begun.
|
||||
func current_wave() -> int:
|
||||
return _wave
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_respawn_cooldown = maxf(0.0, _respawn_cooldown - delta)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.physical_keycode == KEY_R and _respawn_cooldown <= 0.0:
|
||||
_respawn_cooldown = RESPAWN_COOLDOWN
|
||||
reset_spawn()
|
||||
|
||||
|
||||
# Announce a wave and spawn its matadors. Deferred spawn keeps it safe to call
|
||||
# from _ready and from inside a kill handler mid-signal.
|
||||
func _start_wave(number: int) -> void:
|
||||
_wave = number
|
||||
wave_changed.emit(_wave)
|
||||
# Deferred so it's safe from _ready and matches the previous spawn timing.
|
||||
_spawn.call_deferred()
|
||||
|
||||
|
||||
func _spawn() -> void:
|
||||
var positions := _spread_positions(_spawn_count)
|
||||
for pos: Vector3 in positions:
|
||||
# Bull and matadors start at opposite ends of a random diameter, so they always
|
||||
# begin as far apart as the arena allows (edge-to-edge across the centre).
|
||||
var arena_r := DP.f("arena_spawn_radius")
|
||||
var bull_angle := randf() * TAU
|
||||
_place_bull(bull_angle, arena_r)
|
||||
for pos: Vector3 in _spread_positions(_spawn_count, bull_angle + PI, arena_r):
|
||||
_spawn_at(pos)
|
||||
|
||||
|
||||
func _spread_positions(count: int) -> Array[Vector3]:
|
||||
func _place_bull(angle: float, arena_r: float) -> void:
|
||||
var bull := get_tree().get_first_node_in_group(&"player") as Node3D
|
||||
if not bull:
|
||||
return
|
||||
bull.global_position = Vector3(
|
||||
cos(angle) * arena_r, bull.global_position.y, sin(angle) * arena_r
|
||||
)
|
||||
|
||||
|
||||
# Positions for the matadors, clustered on the far side (centred on `center_angle`)
|
||||
# so every matador stays near the point diametrically opposite the bull.
|
||||
func _spread_positions(count: int, center_angle: float, arena_r: float) -> Array[Vector3]:
|
||||
var positions: Array[Vector3] = []
|
||||
var arena_r := DP.f("mat_wander_radius") * 0.72
|
||||
var min_r := 5.0
|
||||
if count <= 1:
|
||||
positions.append(Vector3(cos(center_angle) * arena_r, 1.0, sin(center_angle) * arena_r))
|
||||
return positions
|
||||
var arc := TAU * 0.33
|
||||
for i: int in count:
|
||||
var sector := TAU / count
|
||||
var angle := sector * i + randf_range(-sector * 0.35, sector * 0.35)
|
||||
var radius := randf_range(min_r, arena_r)
|
||||
var t := float(i) / float(count - 1) - 0.5 # -0.5 .. 0.5 across the arc
|
||||
var angle := center_angle + t * arc
|
||||
var radius := randf_range(arena_r * 0.7, arena_r)
|
||||
positions.append(Vector3(cos(angle) * radius, 1.0, sin(angle) * radius))
|
||||
return positions
|
||||
|
||||
@@ -68,18 +60,10 @@ func _spawn_at(pos: Vector3) -> void:
|
||||
_alive += 1
|
||||
|
||||
|
||||
# Every matador down = the bull wins. No waves, no respawns — one clean arena.
|
||||
func _on_matador_killed() -> void:
|
||||
matador_killed.emit()
|
||||
_alive -= 1
|
||||
if _alive <= 0:
|
||||
_spawn_count *= 2
|
||||
_start_wave(_wave + 1)
|
||||
|
||||
|
||||
func reset_spawn() -> void:
|
||||
for m: Node in get_tree().get_nodes_in_group(&"matador"):
|
||||
m.queue_free()
|
||||
await get_tree().process_frame
|
||||
_spawn_count = maxi(1, int(DP.f("mat_spawn_count")))
|
||||
_alive = 0
|
||||
_start_wave(1)
|
||||
if _alive <= 0 and not _resolved:
|
||||
_resolved = true
|
||||
all_defeated.emit()
|
||||
|
||||
@@ -22,12 +22,8 @@ const _ANIM_ROLL: StringName = &"Armature|ROLL"
|
||||
var _hoof_emitters: Array[CPUParticles3D] = []
|
||||
var _legs: Node
|
||||
|
||||
var _kick_timer: float = 0.0
|
||||
var _was_on_floor: bool = false
|
||||
var _pre_slide_vel_y: float = 0.0
|
||||
var _thrust_left_charge: float = 0.0
|
||||
var _thrust_right_charge: float = 0.0
|
||||
var _charge_was_full: bool = false
|
||||
|
||||
var _slam_ring_mesh: ArrayMesh = null
|
||||
|
||||
@@ -38,30 +34,24 @@ var _ability_timer: float = 0.0
|
||||
var _active_ability: int = -1
|
||||
var _dash_dir: Vector3 = Vector3.ZERO
|
||||
var _roll_dir: Vector3 = Vector3.ZERO
|
||||
var _roll_ramp: float = 0.0
|
||||
var _roll_spin: float = 0.0
|
||||
var _bull_anim: AnimationPlayer = null
|
||||
|
||||
var _charge_ready_emitter: CPUParticles3D = null
|
||||
var _slam_pending: bool = false
|
||||
var _charge_trail_emitter: CPUParticles3D = null
|
||||
var _roll_spark_emitter: CPUParticles3D = null
|
||||
var _cube_scale: Vector3 = Vector3.ONE
|
||||
|
||||
# Floating 3D charge bar (mouse-button thrust charge indicator)
|
||||
const _CHARGE_BAR_WIDTH: float = 1.1
|
||||
const _CHARGE_BAR_HEIGHT: float = 1.45
|
||||
var _charge_bar: Node3D = null
|
||||
var _charge_bar_fill: MeshInstance3D = null
|
||||
var _charge_bar_fill_mat: StandardMaterial3D = null
|
||||
var _charge_bar_ramp: Gradient = null
|
||||
var _charge_bar_time: float = 0.0
|
||||
var _charge_bar_shown: float = 0.0
|
||||
|
||||
const MAX_HEALTH: int = 10
|
||||
var health: int = MAX_HEALTH
|
||||
signal health_changed(new_health: int)
|
||||
# One clean hit ends the run — see take_sword_hit(). The HUD listens for `died`
|
||||
# to raise the lose screen (the `cause` tags how it happened, for the death notice);
|
||||
# _dead latches so a flurry of hits fires it only once.
|
||||
signal died(cause: String)
|
||||
var _dead: bool = false
|
||||
|
||||
var _huff_player: AudioStreamPlayer = null
|
||||
var _crowd_player: AudioStreamPlayer = null
|
||||
var _huff_timer: float = 0.0
|
||||
var _roll_damping: float = 0.15
|
||||
|
||||
enum DustState { NONE, WALK, CHARGE }
|
||||
var _dust_state: DustState = DustState.NONE
|
||||
@@ -71,15 +61,15 @@ var _charge_dust_ramp: Gradient = null
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group(&"player")
|
||||
_cube_scale = cube_guy.scale
|
||||
_setup_hoof_dust()
|
||||
_setup_legs()
|
||||
_setup_charge_indicators()
|
||||
_setup_charge_bar()
|
||||
_setup_charge_trail()
|
||||
_setup_roll_sparks()
|
||||
_setup_audio()
|
||||
_bull_anim = _find_anim_player(cube_guy)
|
||||
_setup_bull_idle()
|
||||
DP.any_changed.connect(_on_dp_changed)
|
||||
_roll_damping = DP.f("roll_damping")
|
||||
|
||||
|
||||
func _setup_legs() -> void:
|
||||
@@ -129,39 +119,8 @@ func _setup_hoof_dust() -> void:
|
||||
_hoof_emitters.append(p)
|
||||
|
||||
|
||||
func _setup_charge_indicators() -> void:
|
||||
var sphere := _particle_sphere(0.07, 4, 2, _unshaded_material())
|
||||
|
||||
# Orange spark burst when single-button charge hits max
|
||||
var burst_ramp := Gradient.new()
|
||||
burst_ramp.set_color(0, Color(1.0, 0.55, 0.0, 1.0))
|
||||
burst_ramp.set_color(1, Color(1.0, 0.15, 0.0, 0.0))
|
||||
|
||||
var burst_scale := Curve.new()
|
||||
burst_scale.add_point(Vector2(0.0, 1.0))
|
||||
burst_scale.add_point(Vector2(1.0, 0.0))
|
||||
|
||||
_charge_ready_emitter = CPUParticles3D.new()
|
||||
_charge_ready_emitter.position = Vector3(0.0, 0.3, 0.0)
|
||||
_charge_ready_emitter.emitting = false
|
||||
_charge_ready_emitter.one_shot = true
|
||||
_charge_ready_emitter.amount = 16
|
||||
_charge_ready_emitter.lifetime = 0.5
|
||||
_charge_ready_emitter.explosiveness = 1.0
|
||||
_charge_ready_emitter.randomness = 0.4
|
||||
_charge_ready_emitter.local_coords = false
|
||||
_charge_ready_emitter.direction = Vector3.UP
|
||||
_charge_ready_emitter.spread = 120.0
|
||||
_charge_ready_emitter.initial_velocity_min = 2.5
|
||||
_charge_ready_emitter.initial_velocity_max = 5.0
|
||||
_charge_ready_emitter.scale_amount_min = 0.2
|
||||
_charge_ready_emitter.scale_amount_max = 0.5
|
||||
_charge_ready_emitter.mesh = sphere
|
||||
_charge_ready_emitter.color_ramp = burst_ramp
|
||||
_charge_ready_emitter.scale_amount_curve = burst_scale
|
||||
cube_guy.add_child(_charge_ready_emitter)
|
||||
|
||||
# Blue energy trail — streams backward during charge
|
||||
func _setup_charge_trail() -> void:
|
||||
# Blue energy trail — streams backward during a fast run / roll
|
||||
var trail_sphere := _particle_sphere(0.065, 4, 2, _unshaded_material())
|
||||
|
||||
var trail_ramp := Gradient.new()
|
||||
@@ -194,93 +153,39 @@ func _setup_charge_indicators() -> void:
|
||||
cube_guy.add_child(_charge_trail_emitter)
|
||||
|
||||
|
||||
func _setup_charge_bar() -> void:
|
||||
_charge_bar = Node3D.new()
|
||||
_charge_bar.visible = false
|
||||
add_child(_charge_bar)
|
||||
func _setup_roll_sparks() -> void:
|
||||
# Rammus Powerball ground spray — dirt/sparks flung outward from the spinning
|
||||
# ball. World-space (local_coords off) so they peel off into a trail behind it.
|
||||
var spark := _particle_sphere(0.05, 4, 2, _unshaded_material())
|
||||
|
||||
var w := _CHARGE_BAR_WIDTH
|
||||
var thickness := 0.16
|
||||
var ramp := Gradient.new()
|
||||
ramp.set_color(0, Color(1.00, 0.78, 0.32, 1.0))
|
||||
ramp.set_color(1, Color(0.70, 0.42, 0.14, 0.0))
|
||||
|
||||
# Dark translucent track behind the fill, slightly larger for a border look.
|
||||
var track_mat := StandardMaterial3D.new()
|
||||
track_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
track_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
track_mat.albedo_color = Color(0.02, 0.02, 0.04, 0.55)
|
||||
var track_box := BoxMesh.new()
|
||||
track_box.size = Vector3(w + 0.07, thickness + 0.07, thickness + 0.07)
|
||||
track_box.material = track_mat
|
||||
var track := MeshInstance3D.new()
|
||||
track.mesh = track_box
|
||||
track.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
_charge_bar.add_child(track)
|
||||
var sc := Curve.new()
|
||||
sc.add_point(Vector2(0.0, 0.30))
|
||||
sc.add_point(Vector2(0.3, 1.00))
|
||||
sc.add_point(Vector2(1.0, 0.00))
|
||||
|
||||
# Glowing fill bar; scaled along X each frame to show progress.
|
||||
_charge_bar_fill_mat = StandardMaterial3D.new()
|
||||
_charge_bar_fill_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
_charge_bar_fill_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
_charge_bar_fill_mat.emission_enabled = true
|
||||
var fill_box := BoxMesh.new()
|
||||
fill_box.size = Vector3(w, thickness, thickness)
|
||||
fill_box.material = _charge_bar_fill_mat
|
||||
_charge_bar_fill = MeshInstance3D.new()
|
||||
_charge_bar_fill.mesh = fill_box
|
||||
_charge_bar_fill.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
_charge_bar.add_child(_charge_bar_fill)
|
||||
|
||||
# Cool → hot colour ramp as the charge fills (matches the spark FX palette).
|
||||
_charge_bar_ramp = Gradient.new()
|
||||
_charge_bar_ramp.offsets = PackedFloat32Array([0.0, 0.6, 1.0])
|
||||
_charge_bar_ramp.colors = PackedColorArray([
|
||||
Color(0.25, 0.65, 1.0),
|
||||
Color(1.0, 0.85, 0.2),
|
||||
Color(1.0, 0.28, 0.05),
|
||||
])
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_charge_bar(delta)
|
||||
|
||||
|
||||
func _update_charge_bar(delta: float) -> void:
|
||||
if _charge_bar == null:
|
||||
return
|
||||
_charge_bar_time += delta
|
||||
|
||||
var max_charge := DP.f("thrust_charge_time")
|
||||
var charge := maxf(_thrust_left_charge, _thrust_right_charge)
|
||||
var frac := clampf(charge / max_charge, 0.0, 1.0) if max_charge > 0.0 else 0.0
|
||||
|
||||
# Ease the whole bar in/out so it pops up when charging and fades when done.
|
||||
var target := 1.0 if frac > 0.001 else 0.0
|
||||
_charge_bar_shown = move_toward(_charge_bar_shown, target, delta * 6.0)
|
||||
if _charge_bar_shown <= 0.001:
|
||||
_charge_bar.visible = false
|
||||
return
|
||||
_charge_bar.visible = true
|
||||
|
||||
# Billboard toward the camera on the Y axis, staying upright.
|
||||
var to_cam := camera_pivot.global_position - _charge_bar.global_position
|
||||
_charge_bar.rotation.y = atan2(to_cam.x, to_cam.z)
|
||||
|
||||
# Grow the fill from the left edge.
|
||||
var w := _CHARGE_BAR_WIDTH
|
||||
_charge_bar_fill.scale.x = maxf(frac, 0.0001)
|
||||
_charge_bar_fill.position.x = -0.5 * w + 0.5 * w * frac
|
||||
|
||||
# Colour + glow, with a fast pulse once fully charged.
|
||||
var col := _charge_bar_ramp.sample(frac)
|
||||
var pulse := 1.0 + (0.4 * sin(_charge_bar_time * 16.0) if frac >= 1.0 else 0.0)
|
||||
_charge_bar_fill_mat.albedo_color = Color(col.r, col.g, col.b, 0.95)
|
||||
_charge_bar_fill_mat.emission = col
|
||||
_charge_bar_fill_mat.emission_energy_multiplier = 1.7 * pulse
|
||||
|
||||
# Float above the bull with a gentle bob; a small bounce + rise on the pop-in.
|
||||
var bob := 0.03 * sin(_charge_bar_time * 4.0)
|
||||
_charge_bar.position.y = _CHARGE_BAR_HEIGHT + bob - (1.0 - _charge_bar_shown) * 0.3
|
||||
var appear := _charge_bar_shown * (1.06 - 0.06 * cos(_charge_bar_shown * PI))
|
||||
var full_pop := 1.0 + (0.08 * sin(_charge_bar_time * 16.0) if frac >= 1.0 else 0.0)
|
||||
_charge_bar.scale = Vector3.ONE * appear * full_pop
|
||||
_roll_spark_emitter = CPUParticles3D.new()
|
||||
_roll_spark_emitter.position = Vector3(0.0, 0.10, 0.0)
|
||||
_roll_spark_emitter.emitting = false
|
||||
_roll_spark_emitter.one_shot = false
|
||||
_roll_spark_emitter.amount = 46
|
||||
_roll_spark_emitter.lifetime = 0.45
|
||||
_roll_spark_emitter.randomness = 0.7
|
||||
_roll_spark_emitter.local_coords = false
|
||||
_roll_spark_emitter.direction = Vector3(0.0, 1.0, 0.0)
|
||||
_roll_spark_emitter.spread = 180.0
|
||||
_roll_spark_emitter.gravity = Vector3(0.0, -6.0, 0.0)
|
||||
_roll_spark_emitter.initial_velocity_min = 3.0
|
||||
_roll_spark_emitter.initial_velocity_max = 8.0
|
||||
_roll_spark_emitter.scale_amount_min = 0.5
|
||||
_roll_spark_emitter.scale_amount_max = 1.3
|
||||
_roll_spark_emitter.mesh = spark
|
||||
_roll_spark_emitter.color_ramp = ramp
|
||||
_roll_spark_emitter.scale_amount_curve = sc
|
||||
cube_guy.add_child(_roll_spark_emitter)
|
||||
|
||||
|
||||
func _setup_audio() -> void:
|
||||
@@ -331,7 +236,6 @@ func _set_dust_state(new_state: DustState) -> void:
|
||||
|
||||
|
||||
func _on_dp_changed(_key: String, _val: Variant) -> void:
|
||||
_roll_damping = DP.f("roll_damping")
|
||||
if _hoof_emitters.is_empty() or _dust_state == DustState.NONE:
|
||||
return
|
||||
var prev := _dust_state
|
||||
@@ -460,15 +364,17 @@ func _activate_roll() -> void:
|
||||
_ability_active = true
|
||||
_active_ability = 3
|
||||
_ability_timer = DP.f("roll_duration")
|
||||
# Combine with any charge already underway: keep the current heading + speed,
|
||||
# floored to a launch minimum so even a standing roll shoves off like a boulder.
|
||||
_roll_ramp = 0.0
|
||||
_roll_spin = 0.0
|
||||
# Curl into the ball along the current heading (or facing when standing still)
|
||||
# and launch at the base speed — it ramps up from here the longer you roll.
|
||||
var flat := Vector3(velocity.x, 0.0, velocity.z)
|
||||
if flat.length() > 1.0:
|
||||
_roll_dir = flat.normalized()
|
||||
else:
|
||||
var f := cube_guy.global_transform.basis.z
|
||||
_roll_dir = Vector3(f.x, 0.0, f.z).normalized()
|
||||
var launch := maxf(flat.length(), DP.f("roll_min_speed"))
|
||||
var launch := DP.f("roll_base_speed")
|
||||
velocity.x = _roll_dir.x * launch
|
||||
velocity.z = _roll_dir.z * launch
|
||||
if _bull_anim and _bull_anim.has_animation(_ANIM_ROLL):
|
||||
@@ -476,6 +382,9 @@ func _activate_roll() -> void:
|
||||
_bull_anim.play(_ANIM_ROLL)
|
||||
_legs.set(&"tail_ragdoll", true) # tail streams out behind
|
||||
_legs.set(&"charge_pitch_target", DP.f("charge_head_pitch")) # horns down, plowing
|
||||
_legs.set(&"external_rotation", true) # roll owns the model's full orientation (tumble)
|
||||
if _roll_spark_emitter:
|
||||
_roll_spark_emitter.emitting = true
|
||||
_spawn_dash_burst()
|
||||
camera_pivot.call(&"trigger_hit", 0.35)
|
||||
if _huff_player:
|
||||
@@ -483,27 +392,32 @@ func _activate_roll() -> void:
|
||||
_huff_player.play()
|
||||
|
||||
|
||||
# One rolling frame: steer, ramp/retain speed, move, then ricochet off walls and
|
||||
# bowl through matadors. Fully replaces the normal locomotion path while active.
|
||||
# One rolling frame (Rammus Powerball): steer with momentum, accelerate the longer
|
||||
# the roll lasts, move (walls just slide), then pop the ball on the first matador we
|
||||
# ram. Fully replaces the normal locomotion path while active.
|
||||
func _tick_roll(delta: float, steer: Vector3) -> void:
|
||||
var steer_flat := Vector3(steer.x, 0.0, steer.z)
|
||||
if steer_flat.length() > 0.1:
|
||||
var t := clampf(DP.f("roll_turn") * delta, 0.0, 1.0)
|
||||
_roll_dir = _roll_dir.slerp(steer_flat.normalized(), t).normalized()
|
||||
|
||||
# Ramp toward the cruise top speed; anything above it (from wall boosts) bleeds
|
||||
# off slowly via roll_momentum instead of snapping back — that's the fun carry.
|
||||
var speed := Vector2(velocity.x, velocity.z).length()
|
||||
# Speed builds over time: base → max across roll_rampup_time, so a long roll
|
||||
# winds up into a runaway boulder while a quick tap barely gets going.
|
||||
_roll_ramp += delta
|
||||
var ramp := clampf(_roll_ramp / maxf(DP.f("roll_rampup_time"), 0.01), 0.0, 1.0)
|
||||
var top := DP.f("roll_max_speed")
|
||||
if speed < top:
|
||||
speed = move_toward(speed, top, DP.f("roll_accel") * delta)
|
||||
else:
|
||||
speed = maxf(top, speed * pow(DP.f("roll_momentum"), delta))
|
||||
speed = minf(speed, DP.f("roll_wall_cap"))
|
||||
var speed := lerpf(DP.f("roll_base_speed"), top, ramp)
|
||||
velocity.x = _roll_dir.x * speed
|
||||
velocity.z = _roll_dir.z * speed
|
||||
|
||||
cube_guy.rotation.y = lerp_angle(cube_guy.rotation.y, atan2(_roll_dir.x, _roll_dir.z), delta * 12.0)
|
||||
# Tumble the whole model like a Powerball: face the travel direction (yaw) then
|
||||
# spin forward about the horizontal axis perpendicular to it, angular speed = v/r
|
||||
# so it "rolls" without slipping. Legs yield rotation control while active.
|
||||
var radius := maxf(DP.f("roll_spin_radius"), 0.1)
|
||||
_roll_spin = wrapf(_roll_spin + (speed / radius) * delta, 0.0, TAU)
|
||||
var facing := Basis(Vector3.UP, atan2(_roll_dir.x, _roll_dir.z))
|
||||
var tumble_axis := Vector3.UP.cross(_roll_dir).normalized()
|
||||
cube_guy.basis = (Basis(tumble_axis, _roll_spin) * facing).scaled(_cube_scale)
|
||||
if _bull_anim and _bull_anim.has_animation(_ANIM_ROLL):
|
||||
_bull_anim.speed_scale = clampf(speed / maxf(top, 1.0), 0.6, 2.4)
|
||||
|
||||
@@ -511,40 +425,46 @@ func _tick_roll(delta: float, steer: Vector3) -> void:
|
||||
if _charge_trail_emitter:
|
||||
_charge_trail_emitter.direction = (-_roll_dir + Vector3(0.0, 0.25, 0.0)).normalized()
|
||||
|
||||
var pre_wall := Vector3(velocity.x, 0.0, velocity.z)
|
||||
move_and_slide()
|
||||
_was_on_floor = is_on_floor()
|
||||
_roll_wall_ricochet(pre_wall)
|
||||
# Bowling strike: fling matadors we plow into up and outward like pins (and the
|
||||
# resulting kills feed the HUD combo meter).
|
||||
_hit_matadors_radius(DP.f("roll_hit_radius"), DP.f("roll_hit_strength"), DP.f("roll_hit_up"))
|
||||
_roll_try_pop(speed)
|
||||
|
||||
|
||||
# On hitting a wall mid-roll, mirror the heading and BOOST speed (capped) instead of
|
||||
# bleeding it — banking off walls is how you build ludicrous momentum.
|
||||
func _roll_wall_ricochet(pre_wall: Vector3) -> void:
|
||||
for i in get_slide_collision_count():
|
||||
var col := get_slide_collision(i)
|
||||
var normal := col.get_normal()
|
||||
if absf(normal.y) >= 0.5:
|
||||
# Pop the ball on ramming a matador: launch every matador in the hit radius up and
|
||||
# outward, then end the roll (Rammus stops when Powerball connects). Returns true if
|
||||
# it popped so the caller can stop touching roll state this frame.
|
||||
func _roll_try_pop(speed: float) -> bool:
|
||||
var radius := DP.f("roll_hit_radius")
|
||||
for mat: Node in get_tree().get_nodes_in_group(&"matador"):
|
||||
var to_mat: Vector3 = (mat as Node3D).global_position - global_position
|
||||
to_mat.y = 0.0
|
||||
if to_mat.length() > radius:
|
||||
continue
|
||||
var flat_n := Vector3(normal.x, 0.0, normal.z).normalized()
|
||||
var refl := pre_wall - 2.0 * pre_wall.dot(flat_n) * flat_n
|
||||
refl.y = 0.0
|
||||
if refl.length() < 0.01:
|
||||
return
|
||||
_roll_dir = refl.normalized()
|
||||
var boosted := minf(pre_wall.length() * DP.f("roll_wall_boost"), DP.f("roll_wall_cap"))
|
||||
velocity.x = _roll_dir.x * boosted
|
||||
velocity.z = _roll_dir.z * boosted
|
||||
var hit := col.get_position()
|
||||
_spawn_kick_sparks(hit, flat_n)
|
||||
_spawn_kick_flash(hit)
|
||||
camera_pivot.call(&"trigger_hit", clampf(boosted / 90.0, 0.25, 1.0))
|
||||
_hit_matadors_radius(radius, DP.f("roll_hit_strength"), DP.f("roll_knockup"))
|
||||
camera_pivot.call(&"trigger_hit", clampf(speed / 90.0, 0.35, 1.0))
|
||||
if _huff_player:
|
||||
_huff_player.pitch_scale = randf_range(1.05, 1.35)
|
||||
_huff_player.play()
|
||||
return
|
||||
_end_roll()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
# End the Powerball early: brake hard (the ball "pops" and unrolls) and drop back to
|
||||
# the normal locomotion path next frame.
|
||||
func _end_roll() -> void:
|
||||
_ability_active = false
|
||||
_active_ability = -1
|
||||
_ability_timer = 0.0
|
||||
_roll_ramp = 0.0
|
||||
_legs.set(&"charge_pitch_target", 0.0)
|
||||
_legs.set(&"tail_ragdoll", false)
|
||||
_legs.set(&"external_rotation", false)
|
||||
if _roll_spark_emitter:
|
||||
_roll_spark_emitter.emitting = false
|
||||
velocity.x *= 0.15
|
||||
velocity.z *= 0.15
|
||||
_play_idle()
|
||||
|
||||
|
||||
func _hit_matadors_cone(range_m: float, half_angle_rad: float, strength: float,
|
||||
@@ -884,36 +804,6 @@ func _make_slam_ring_mesh() -> ArrayMesh:
|
||||
return mesh
|
||||
|
||||
|
||||
func _apply_thrust(dir: Vector3) -> void:
|
||||
var impulse := DP.f("thrust_impulse")
|
||||
velocity.x += dir.x * impulse
|
||||
velocity.z += dir.z * impulse
|
||||
if is_on_floor():
|
||||
velocity.y += DP.f("thrust_vertical")
|
||||
var flat := Vector2(velocity.x, velocity.z)
|
||||
var spd := flat.length()
|
||||
if spd > DP.f("max_speed"):
|
||||
var s := DP.f("max_speed") / spd
|
||||
velocity.x *= s
|
||||
velocity.z *= s
|
||||
|
||||
|
||||
func _apply_kick(direction: Vector3) -> void:
|
||||
var force := DP.f("kick_impulse") * randf_range(1.0 - DP.f("kick_force_var"), 1.0 + DP.f("kick_force_var"))
|
||||
var spread := deg_to_rad(randf_range(-DP.f("kick_spread"), DP.f("kick_spread")))
|
||||
var kick_dir := direction.rotated(Vector3.UP, spread)
|
||||
velocity.x += kick_dir.x * force
|
||||
velocity.z += kick_dir.z * force
|
||||
if is_on_floor():
|
||||
velocity.y += DP.f("kick_vertical") * randf_range(0.75, 1.25)
|
||||
var flat := Vector2(velocity.x, velocity.z)
|
||||
var spd := flat.length()
|
||||
if spd > DP.f("max_speed"):
|
||||
var s := DP.f("max_speed") / spd
|
||||
velocity.x *= s
|
||||
velocity.z *= s
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# ── Gravity ───────────────────────────────────────────────────────────────
|
||||
if not is_on_floor():
|
||||
@@ -939,12 +829,15 @@ func _physics_process(delta: float) -> void:
|
||||
if _ability_active:
|
||||
_ability_timer -= delta
|
||||
if _ability_timer <= 0.0:
|
||||
_ability_active = false
|
||||
_active_ability = -1
|
||||
_legs.set(&"charge_pitch_target", 0.0)
|
||||
_legs.set(&"tail_ragdoll", false)
|
||||
_slam_pending = false
|
||||
_play_idle()
|
||||
if _active_ability == 3:
|
||||
_end_roll() # tears down roll-specific state (sparks, tumble control)
|
||||
else:
|
||||
_ability_active = false
|
||||
_active_ability = -1
|
||||
_legs.set(&"charge_pitch_target", 0.0)
|
||||
_legs.set(&"tail_ragdoll", false)
|
||||
_slam_pending = false
|
||||
_play_idle()
|
||||
|
||||
# ── Boulder roll ──────────────────────────────────────────────────────────
|
||||
# While rolling, the ability owns movement entirely (its own accel, low friction
|
||||
@@ -953,56 +846,23 @@ func _physics_process(delta: float) -> void:
|
||||
_tick_roll(delta, direction)
|
||||
return
|
||||
|
||||
# ── Mouse button single-thrust actions ───────────────────────────────────────
|
||||
# Left only → thrust NE (forward-right diagonal)
|
||||
# Right only → thrust NW (forward-left diagonal)
|
||||
var left_held := Input.is_action_pressed(&"thrust_left")
|
||||
var right_held := Input.is_action_pressed(&"thrust_right")
|
||||
|
||||
if left_held and right_held:
|
||||
_thrust_left_charge = 0.0
|
||||
_thrust_right_charge = 0.0
|
||||
else:
|
||||
if left_held:
|
||||
_thrust_left_charge = minf(_thrust_left_charge + delta, DP.f("thrust_charge_time"))
|
||||
elif Input.is_action_just_released(&"thrust_left") and _thrust_left_charge > 0.0:
|
||||
var t := _thrust_left_charge / DP.f("thrust_charge_time")
|
||||
var angle := lerpf(deg_to_rad(DP.f("thrust_min_angle")), deg_to_rad(DP.f("thrust_max_angle")), t)
|
||||
var raw := cube_guy.global_transform.basis.z
|
||||
_apply_thrust(Vector3(raw.x, 0.0, raw.z).normalized().rotated(Vector3.UP, -angle))
|
||||
_thrust_left_charge = 0.0
|
||||
|
||||
if right_held:
|
||||
_thrust_right_charge = minf(_thrust_right_charge + delta, DP.f("thrust_charge_time"))
|
||||
elif Input.is_action_just_released(&"thrust_right") and _thrust_right_charge > 0.0:
|
||||
var t := _thrust_right_charge / DP.f("thrust_charge_time")
|
||||
var angle := lerpf(deg_to_rad(DP.f("thrust_min_angle")), deg_to_rad(DP.f("thrust_max_angle")), t)
|
||||
var raw := cube_guy.global_transform.basis.z
|
||||
_apply_thrust(Vector3(raw.x, 0.0, raw.z).normalized().rotated(Vector3.UP, angle))
|
||||
_thrust_right_charge = 0.0
|
||||
|
||||
# Fire spark burst the frame either single-button charge first hits max
|
||||
var max_charge := DP.f("thrust_charge_time")
|
||||
var either_full := _thrust_left_charge >= max_charge or _thrust_right_charge >= max_charge
|
||||
if either_full and not _charge_was_full:
|
||||
_charge_ready_emitter.restart()
|
||||
_charge_was_full = either_full
|
||||
|
||||
# Reset kick timer on landing so the first kick after a bounce is immediate
|
||||
if on_floor and not _was_on_floor and direction:
|
||||
_kick_timer = 0.0
|
||||
_was_on_floor = on_floor
|
||||
|
||||
# ── Periodic kick ─────────────────────────────────────────────────────────
|
||||
_kick_timer = maxf(0.0, _kick_timer - delta)
|
||||
if direction and _kick_timer <= 0.0:
|
||||
_apply_kick(direction)
|
||||
_kick_timer = DP.f("kick_interval")
|
||||
|
||||
# ── Rolling friction (exponential, frame-rate independent) ────────────────
|
||||
var retain := pow(_roll_damping, delta)
|
||||
velocity.x *= retain
|
||||
velocity.z *= retain
|
||||
# ── Ground locomotion — smooth accelerate / friction ──────────────────────
|
||||
# WASD/arrows drive the bull directly: hold a direction to ramp up to max_speed
|
||||
# and steer, release to coast to a stop. Ability bursts (dash) and wall bounces
|
||||
# can exceed max_speed — while a direction is held we steer that momentum without
|
||||
# braking it (cap tracks the current speed); friction only bleeds the excess once
|
||||
# input is released.
|
||||
var flat_vel := Vector3(velocity.x, 0.0, velocity.z)
|
||||
var max_speed := DP.f("max_speed")
|
||||
if direction.length_squared() > 0.01:
|
||||
var cap := maxf(max_speed, flat_vel.length())
|
||||
flat_vel = flat_vel.move_toward(direction * cap, DP.f("move_accel") * delta)
|
||||
else:
|
||||
flat_vel = flat_vel.move_toward(Vector3.ZERO, DP.f("move_friction") * delta)
|
||||
velocity.x = flat_vel.x
|
||||
velocity.z = flat_vel.z
|
||||
|
||||
# ── Visual rotation follows velocity direction ─────────────────────────────
|
||||
var flat_speed := Vector2(velocity.x, velocity.z).length()
|
||||
@@ -1065,6 +925,11 @@ func _physics_process(delta: float) -> void:
|
||||
# ── Hoof sounds ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
func take_sword_hit() -> void:
|
||||
health = maxi(0, health - 1)
|
||||
health_changed.emit(health)
|
||||
# A single clean hit — thrown or swung blade — ends the run. `cause` ("gored" /
|
||||
# "thrown") tags how it happened for the death notice. Latches so the lose screen is
|
||||
# raised exactly once even if several blades land on the same frame.
|
||||
func take_sword_hit(cause: String = "") -> void:
|
||||
if _dead:
|
||||
return
|
||||
_dead = true
|
||||
died.emit(cause)
|
||||
|
||||
@@ -11,6 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="Bullosseum"
|
||||
config/version="0.1.0"
|
||||
run/main_scene="res://MainMenu.tscn"
|
||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
@@ -76,18 +77,6 @@ toggle_mouse_capture={
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":4,"position":Vector2(461, 22),"global_position":Vector2(470, 70),"factor":1.0,"button_index":3,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
thrust_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
thrust_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":2,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
ability_kick={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
extends SceneTree
|
||||
## Headless difficulty simulator.
|
||||
##
|
||||
## Drives a kinematic "bot bull" against ONE real matador (the actual Matador.tscn
|
||||
## + matador.gd AI) across a few behaviour profiles, many trials each, and reports
|
||||
## how often the matador wins — i.e. gores the bull — versus how often the bull
|
||||
## wins by ramming the matador. It exists so matador difficulty can be tuned from
|
||||
## measurements instead of guesswork.
|
||||
##
|
||||
## godot --headless --script tests/difficulty_sim.gd
|
||||
##
|
||||
## Read the CHARGER row: that models a player who just charges in. If the matador
|
||||
## almost never wins there, blind charging is unpunished and the game is too easy.
|
||||
|
||||
const CHARGER_TRIALS := 16
|
||||
const KITER_TRIALS := 16
|
||||
const PASSIVE_TRIALS := 8
|
||||
const TRIAL_TIMEOUT := 5.0
|
||||
const SPAWN_SETTLE := 0.4
|
||||
|
||||
const CHARGE_SPEED := 42.0 # a fast, committed horn-charge
|
||||
const KITE_DIST := 12.0
|
||||
const KITE_SPEED := 17.0 # a real bull easily out-paces the matador; only throws threaten
|
||||
const START_DIST := 12.0
|
||||
|
||||
var _scene: Node
|
||||
var _dp: Node
|
||||
var _mat_scene: PackedScene
|
||||
var _player: CharacterBody3D
|
||||
var _matador: Node3D
|
||||
var _bull_dead := false
|
||||
var _mat_dead := false
|
||||
var _dt := 1.0 / 60.0
|
||||
|
||||
# Charger bull state
|
||||
var _charge_dir: Vector3 = Vector3.ZERO
|
||||
var _charge_timer: float = 0.0
|
||||
var _recover_timer: float = 0.0
|
||||
# Kiter juke state
|
||||
var _juke_dir: float = 1.0
|
||||
var _juke_timer: float = 0.0
|
||||
var _juke_range: float = KITE_DIST
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_dt = 1.0 / float(Engine.physics_ticks_per_second)
|
||||
_dp = root.get_node_or_null("/root/DP")
|
||||
_mat_scene = load("res://Matador.tscn") as PackedScene
|
||||
|
||||
_scene = (load("res://scene.tscn") as PackedScene).instantiate()
|
||||
root.add_child(_scene)
|
||||
current_scene = _scene
|
||||
await create_timer(0.5).timeout
|
||||
|
||||
var players := get_nodes_in_group(&"player")
|
||||
if players.is_empty():
|
||||
push_error("no player in scene"); quit(1); return
|
||||
_player = players[0]
|
||||
_player.set_physics_process(false) # we drive the bull kinematically
|
||||
_player.died.connect(func(_cause: String) -> void: _bull_dead = true)
|
||||
|
||||
_strip_match_machinery() # no HUD pause / spawner interference
|
||||
|
||||
print("=".repeat(64))
|
||||
print("DIFFICULTY SIMULATION (physics %d Hz)" % Engine.physics_ticks_per_second)
|
||||
print("=".repeat(64))
|
||||
|
||||
var charger := await _run_profile("CHARGER (fast straight charges)", CHARGER_TRIALS, &"charger")
|
||||
var kiter := await _run_profile("KITER (circles at ~11 m)", KITER_TRIALS, &"kiter")
|
||||
var passive := await _run_profile("PASSIVE (holds at attack range)", PASSIVE_TRIALS, &"passive")
|
||||
|
||||
print("\n" + "=".repeat(64))
|
||||
print("SUMMARY — matador win %% (bull gored)")
|
||||
print("=".repeat(64))
|
||||
_print_row("CHARGER", charger)
|
||||
_print_row("KITER ", kiter)
|
||||
_print_row("PASSIVE", passive)
|
||||
print("=".repeat(64))
|
||||
quit(0)
|
||||
|
||||
|
||||
var _state_hist: Dictionary = {}
|
||||
var _throws: int = 0
|
||||
var _diag_printed: bool = false
|
||||
|
||||
|
||||
func _run_profile(label: String, trials: int, mode: StringName) -> Dictionary:
|
||||
var wins := 0
|
||||
var losses := 0
|
||||
var timeouts := 0
|
||||
var total_t := 0.0
|
||||
_state_hist = {}
|
||||
_throws = 0
|
||||
for _i in trials:
|
||||
var res := await _run_trial(mode)
|
||||
total_t += res[1] as float
|
||||
match res[0] as StringName:
|
||||
&"mat": wins += 1
|
||||
&"bull": losses += 1
|
||||
_: timeouts += 1
|
||||
print("\n-- %s --" % label)
|
||||
print(" matador wins: %d/%d bull wins: %d timeouts: %d avg %.2fs" % [
|
||||
wins, trials, losses, timeouts, total_t / maxf(float(trials), 1.0)])
|
||||
print(" swords thrown: %d states: %s" % [_throws, _fmt_hist()])
|
||||
return {"win": wins, "loss": losses, "timeout": timeouts, "n": trials}
|
||||
|
||||
|
||||
func _fmt_hist() -> String:
|
||||
var parts: Array[String] = []
|
||||
var total := 0
|
||||
for k: String in _state_hist:
|
||||
total += _state_hist[k] as int
|
||||
for k: String in _state_hist:
|
||||
parts.append("%s %.0f%%" % [k, 100.0 * float(_state_hist[k]) / maxf(float(total), 1.0)])
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
func _count_thrown_swords() -> int:
|
||||
var n := 0
|
||||
for c: Node in _scene.get_children():
|
||||
if c is RigidBody3D:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
func _run_trial(mode: StringName) -> Array:
|
||||
if is_instance_valid(_matador):
|
||||
_matador.queue_free()
|
||||
await physics_frame
|
||||
|
||||
_matador = _mat_scene.instantiate()
|
||||
_scene.add_child(_matador)
|
||||
_matador.global_position = Vector3(0.0, 1.0, 0.0)
|
||||
_matador.killed.connect(func() -> void: _mat_dead = true)
|
||||
|
||||
var ang := randf() * TAU
|
||||
_player.global_position = Vector3(cos(ang), 0.0, sin(ang)) * START_DIST + Vector3(0.0, 1.0, 0.0)
|
||||
_player.velocity = Vector3.ZERO
|
||||
_player._dead = false
|
||||
_bull_dead = false
|
||||
_mat_dead = false
|
||||
_charge_dir = Vector3.ZERO
|
||||
_charge_timer = 0.0
|
||||
_recover_timer = 0.0
|
||||
_juke_dir = 1.0 if randf() < 0.5 else -1.0
|
||||
_juke_timer = 0.0
|
||||
_juke_range = KITE_DIST
|
||||
|
||||
# Let the matador's _ready (ragdoll rig, sword) settle before the duel counts.
|
||||
var settle := SPAWN_SETTLE
|
||||
while settle > 0.0:
|
||||
await physics_frame
|
||||
settle -= _dt
|
||||
_bull_dead = false
|
||||
_mat_dead = false
|
||||
_player._dead = false
|
||||
|
||||
if not _diag_printed:
|
||||
_diag_printed = true
|
||||
print(" [diag] matador has_sword=%s in_hand=%s throw_range=[%.0f,%.0f]" % [
|
||||
is_instance_valid(_matador._sword_node), _matador._sword_in_hand,
|
||||
_dp.f("mat_throw_min_dist"), _dp.f("mat_throw_range")])
|
||||
|
||||
var t := 0.0
|
||||
var max_swords := 0
|
||||
while t < TRIAL_TIMEOUT:
|
||||
_drive(mode)
|
||||
await physics_frame
|
||||
t += _dt
|
||||
var s: String = _matador.ai_state_name()
|
||||
_state_hist[s] = int(_state_hist.get(s, 0)) + 1
|
||||
max_swords = maxi(max_swords, _count_thrown_swords())
|
||||
if _bull_dead: # the bull was gored → matador win
|
||||
_throws += max_swords
|
||||
return [&"mat", t]
|
||||
if _mat_dead: # the matador was rammed → bull win
|
||||
_throws += max_swords
|
||||
return [&"bull", t]
|
||||
_throws += max_swords
|
||||
return [&"timeout", t]
|
||||
|
||||
|
||||
func _drive(mode: StringName) -> void:
|
||||
var to_mat := _matador.global_position - _player.global_position
|
||||
to_mat.y = 0.0
|
||||
var dist := to_mat.length()
|
||||
var aim := to_mat.normalized() if dist > 0.05 else Vector3.FORWARD
|
||||
var vel := Vector3.ZERO
|
||||
|
||||
match mode:
|
||||
&"charger":
|
||||
if _recover_timer > 0.0:
|
||||
_recover_timer -= _dt
|
||||
vel = -aim * 14.0
|
||||
else:
|
||||
if _charge_timer <= 0.0:
|
||||
_charge_dir = aim # commit a straight line (dodgeable)
|
||||
_charge_timer = 0.6
|
||||
_charge_timer -= _dt
|
||||
vel = _charge_dir * CHARGE_SPEED
|
||||
if dist < 0.8 or _charge_timer <= 0.0:
|
||||
_recover_timer = 0.35
|
||||
_charge_timer = 0.0
|
||||
&"kiter":
|
||||
# Juke: flip strafe direction and shift the hold distance at random so the
|
||||
# matador can't perfectly lead the throw — models an evasive player.
|
||||
_juke_timer -= _dt
|
||||
if _juke_timer <= 0.0:
|
||||
_juke_timer = randf_range(0.4, 0.9)
|
||||
_juke_dir = -_juke_dir if randf() < 0.6 else _juke_dir
|
||||
_juke_range = randf_range(8.0, 13.0)
|
||||
var inward := aim * clampf(dist - _juke_range, -1.0, 1.0)
|
||||
var tangent := Vector3(-aim.z, 0.0, aim.x) * _juke_dir
|
||||
vel = (tangent + inward).normalized() * KITE_SPEED
|
||||
&"passive":
|
||||
# Sit just inside the matador's attack range so it commits to a strike.
|
||||
if dist > 6.5:
|
||||
vel = aim * 6.0
|
||||
elif dist < 5.0:
|
||||
vel = -aim * 4.0
|
||||
|
||||
_player.velocity = Vector3(vel.x, 0.0, vel.z)
|
||||
_player.move_and_slide()
|
||||
var p := _player.global_position
|
||||
p.y = 1.0
|
||||
_player.global_position = p
|
||||
|
||||
|
||||
func _strip_match_machinery() -> void:
|
||||
for m: Node in get_nodes_in_group(&"matador"):
|
||||
m.queue_free()
|
||||
for s: Node in get_nodes_in_group(&"matador_spawn"):
|
||||
s.queue_free()
|
||||
var hud := _find_hud(_scene)
|
||||
if hud != null:
|
||||
hud.queue_free()
|
||||
|
||||
|
||||
func _find_hud(n: Node) -> Node:
|
||||
if n is CanvasLayer and n.has_method(&"_show_game_over"):
|
||||
return n
|
||||
for c: Node in n.get_children():
|
||||
var r := _find_hud(c)
|
||||
if r != null:
|
||||
return r
|
||||
return null
|
||||
|
||||
|
||||
func _print_row(label: String, r: Dictionary) -> void:
|
||||
var n: int = r["n"]
|
||||
var win: int = r["win"]
|
||||
var pct := 100.0 * float(win) / maxf(float(n), 1.0)
|
||||
print(" %s %5.1f%% (%d win / %d loss / %d timeout)" % [
|
||||
label, pct, win, r["loss"], r["timeout"]])
|
||||