Compare commits
51 Commits
0.2.0
..
bd10d1789b
| Author | SHA1 | Date | |
|---|---|---|---|
| bd10d1789b | |||
| 170d8ff3f1 | |||
| 550bf2c5ad | |||
| 02f3e283de | |||
| 9123574947 | |||
| aadf59d2d0 | |||
| d57c3dc2a2 | |||
| 09ae6bfbf4 | |||
| 40c1b8e680 | |||
| a1c77fed5e | |||
| 1a8af92d88 | |||
| 8166a6c18e | |||
| 46d6dda325 | |||
| fc9348551d | |||
| c4a19c7443 | |||
| 95c4c0ddf9 | |||
| 6a4f104655 | |||
| d4b67888d7 | |||
| b177e0aee3 | |||
| 2a6f4c0f7b | |||
| 32d0c58f5c | |||
| 251002ea7c | |||
| 986496097f | |||
| d976deeb6a | |||
| d632670092 | |||
| e1899e49f4 | |||
| 6f5f9e6bba | |||
| 91cd027a98 | |||
| ef2fb24969 | |||
| 4c75776c70 | |||
| 7688084b7e | |||
| ccc92f201f | |||
| 008650a87e | |||
| b9e3147519 | |||
| cdba320cbc | |||
| ee7b1617c1 | |||
| d749485126 | |||
| 45427b7070 | |||
| d514435dd8 | |||
| 3eb9fa50f3 | |||
| aee900c009 | |||
| 4cf714f516 | |||
| b78736a1f4 | |||
| 212263c858 | |||
| 8c4eeb0861 | |||
| 95def2d527 | |||
| 9a43a9ff8b | |||
| 7238cbf49e | |||
| 44c667f097 | |||
| 3cd347d420 | |||
| 6414b2e7cc |
@@ -4,8 +4,9 @@ importer="texture"
|
|||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://dk6xuwwa0mfpe"
|
uid="uid://dk6xuwwa0mfpe"
|
||||||
path.s3tc="res://.godot/imported/Arena_sand.png-416de2afe03241df088061e1a5f831af.s3tc.ctex"
|
path.s3tc="res://.godot/imported/Arena_sand.png-416de2afe03241df088061e1a5f831af.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Arena_sand.png-416de2afe03241df088061e1a5f831af.etc2.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"imported_formats": ["s3tc_bptc"],
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
"vram_texture": true
|
"vram_texture": true
|
||||||
}
|
}
|
||||||
generator_parameters={
|
generator_parameters={
|
||||||
@@ -15,7 +16,7 @@ generator_parameters={
|
|||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Assets/Arena_sand.png"
|
source_file="res://Assets/Arena_sand.png"
|
||||||
dest_files=["res://.godot/imported/Arena_sand.png-416de2afe03241df088061e1a5f831af.s3tc.ctex"]
|
dest_files=["res://.godot/imported/Arena_sand.png-416de2afe03241df088061e1a5f831af.s3tc.ctex", "res://.godot/imported/Arena_sand.png-416de2afe03241df088061e1a5f831af.etc2.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ importer="texture"
|
|||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://b4gkpa7jypcsm"
|
uid="uid://b4gkpa7jypcsm"
|
||||||
path.s3tc="res://.godot/imported/Arena_wall.png-e67bd4bbd2f60583bfc14dd8b7687eef.s3tc.ctex"
|
path.s3tc="res://.godot/imported/Arena_wall.png-e67bd4bbd2f60583bfc14dd8b7687eef.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Arena_wall.png-e67bd4bbd2f60583bfc14dd8b7687eef.etc2.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"imported_formats": ["s3tc_bptc"],
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
"vram_texture": true
|
"vram_texture": true
|
||||||
}
|
}
|
||||||
generator_parameters={
|
generator_parameters={
|
||||||
@@ -15,7 +16,7 @@ generator_parameters={
|
|||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Assets/Arena_wall.png"
|
source_file="res://Assets/Arena_wall.png"
|
||||||
dest_files=["res://.godot/imported/Arena_wall.png-e67bd4bbd2f60583bfc14dd8b7687eef.s3tc.ctex"]
|
dest_files=["res://.godot/imported/Arena_wall.png-e67bd4bbd2f60583bfc14dd8b7687eef.s3tc.ctex", "res://.godot/imported/Arena_wall.png-e67bd4bbd2f60583bfc14dd8b7687eef.etc2.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ importer="texture"
|
|||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://yercpq3y2o0x"
|
uid="uid://yercpq3y2o0x"
|
||||||
path.s3tc="res://.godot/imported/Arena_wood.png-b4ed20d66b4d375396b0be2f2f863d80.s3tc.ctex"
|
path.s3tc="res://.godot/imported/Arena_wood.png-b4ed20d66b4d375396b0be2f2f863d80.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Arena_wood.png-b4ed20d66b4d375396b0be2f2f863d80.etc2.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"imported_formats": ["s3tc_bptc"],
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
"vram_texture": true
|
"vram_texture": true
|
||||||
}
|
}
|
||||||
generator_parameters={
|
generator_parameters={
|
||||||
@@ -15,7 +16,7 @@ generator_parameters={
|
|||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Assets/Arena_wood.png"
|
source_file="res://Assets/Arena_wood.png"
|
||||||
dest_files=["res://.godot/imported/Arena_wood.png-b4ed20d66b4d375396b0be2f2f863d80.s3tc.ctex"]
|
dest_files=["res://.godot/imported/Arena_wood.png-b4ed20d66b4d375396b0be2f2f863d80.s3tc.ctex", "res://.godot/imported/Arena_wood.png-b4ed20d66b4d375396b0be2f2f863d80.etc2.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ch1wqha62vhtb"
|
||||||
|
path="res://.godot/imported/IG_square.png-c38dd1a0a4601b6ff4ffec2910ed2ce0.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/IG_square.png"
|
||||||
|
dest_files=["res://.godot/imported/IG_square.png-c38dd1a0a4601b6ff4ffec2910ed2ce0.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
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
importer="scene"
|
importer="scene"
|
||||||
importer_version=1
|
importer_version=1
|
||||||
type="PackedScene"
|
type="PackedScene"
|
||||||
uid="uid://dxe72ig8gfbcg"
|
uid="uid://cwmo5vh738x6w"
|
||||||
path="res://.godot/imported/arena model2026.glb-299db01cae891e5005f8ac1e6c83d0a6.scn"
|
path="res://.godot/imported/Lobby_level.glb-a7e896ecf7377d9530a80d5e63140cc3.scn"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Assets/arena model2026.glb"
|
source_file="res://Assets/Lobby_level.glb"
|
||||||
dest_files=["res://.godot/imported/arena model2026.glb-299db01cae891e5005f8ac1e6c83d0a6.scn"]
|
dest_files=["res://.godot/imported/Lobby_level.glb-a7e896ecf7377d9530a80d5e63140cc3.scn"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 598 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://xtv6u00w4nmy"
|
||||||
|
path.s3tc="res://.godot/imported/Lobby_level_sand.png-bb70b0169c6b5159e020ce143afe9786.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Lobby_level_sand.png-bb70b0169c6b5159e020ce143afe9786.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "c8b9b7d6205ce80dfcfaa38152b996fe"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/Lobby_level_sand.png"
|
||||||
|
dest_files=["res://.godot/imported/Lobby_level_sand.png-bb70b0169c6b5159e020ce143afe9786.s3tc.ctex", "res://.godot/imported/Lobby_level_sand.png-bb70b0169c6b5159e020ce143afe9786.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
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=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
|
After Width: | Height: | Size: 412 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://csy5f3l5lufp5"
|
||||||
|
path.s3tc="res://.godot/imported/Lobby_level_wall.png-39243638bfc6ea5742b6e2fdc927ae2c.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Lobby_level_wall.png-39243638bfc6ea5742b6e2fdc927ae2c.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "0e7ed34c3c812d96732bd32f9dca0c93"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/Lobby_level_wall.png"
|
||||||
|
dest_files=["res://.godot/imported/Lobby_level_wall.png-39243638bfc6ea5742b6e2fdc927ae2c.s3tc.ctex", "res://.godot/imported/Lobby_level_wall.png-39243638bfc6ea5742b6e2fdc927ae2c.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
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=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
|
After Width: | Height: | Size: 180 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ym5273astq46"
|
||||||
|
path.s3tc="res://.godot/imported/Lobby_level_wood.png-4817792c09af3fd5e04ad29e0f9bd0ce.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Lobby_level_wood.png-4817792c09af3fd5e04ad29e0f9bd0ce.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "23a7a951553ffe271c47693fd71314ca"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/Lobby_level_wood.png"
|
||||||
|
dest_files=["res://.godot/imported/Lobby_level_wood.png-4817792c09af3fd5e04ad29e0f9bd0ce.s3tc.ctex", "res://.godot/imported/Lobby_level_wood.png-4817792c09af3fd5e04ad29e0f9bd0ce.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
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=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
@@ -28,7 +28,7 @@ meshes/generate_lods=true
|
|||||||
meshes/create_shadow_meshes=true
|
meshes/create_shadow_meshes=true
|
||||||
meshes/light_baking=1
|
meshes/light_baking=1
|
||||||
meshes/lightmap_texel_size=0.2
|
meshes/lightmap_texel_size=0.2
|
||||||
meshes/force_disable_compression=false
|
meshes/force_disable_compression=true
|
||||||
skins/use_named_skins=true
|
skins/use_named_skins=true
|
||||||
animation/import=true
|
animation/import=true
|
||||||
animation/fps=30
|
animation/fps=30
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
[gd_scene format=3 uid="uid://by4ifsgxlv0gj"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bm6rmt3xprhjs" path="res://Assets/bear_level_arena.glb" id="1_beararena"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_floor"]
|
||||||
|
|
||||||
|
[node name="bear_level" type="Node3D" unique_id=2020009824]
|
||||||
|
|
||||||
|
[node name="Node3D" type="Node3D" parent="." unique_id=1366355859]
|
||||||
|
|
||||||
|
[node name="StaticBody3D" type="StaticBody3D" parent="Node3D" unique_id=721553462]
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Node3D/StaticBody3D" unique_id=474515358]
|
||||||
|
transform = Transform3D(30, 0, 0, 0, 1, 0, 0, 0, 30, 0, -0.5, 0)
|
||||||
|
shape = SubResource("BoxShape3D_floor")
|
||||||
|
|
||||||
|
[node name="bear_level_arena" parent="." unique_id=948414670 instance=ExtResource("1_beararena")]
|
||||||
|
transform = Transform3D(2.875, 0, 0, 0, 2.875, 0, 0, 0, 2.875, 0, 0, 0)
|
||||||
|
|
||||||
|
[node name="MatadorSpawner" type="Node3D" parent="." unique_id=1869467620]
|
||||||
|
|
||||||
|
[node name="Lights" type="Node3D" parent="." unique_id=1435665429]
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Lights" unique_id=1814200565]
|
||||||
|
transform = Transform3D(0.01535992, 0.56825536, -0.82270896, 0.16280846, 0.8104039, 0.56279576, 0.9865382, -0.14258848, -0.08006904, -4.08134, 9.197985, 0.97087634)
|
||||||
|
light_energy = 0.4
|
||||||
|
light_indirect_energy = 0.767
|
||||||
|
shadow_blur = 0.47
|
||||||
|
|
||||||
|
[node name="SpotLight3D2" type="SpotLight3D" parent="Lights" unique_id=2071037822]
|
||||||
|
transform = Transform3D(0.904133, 0.30962402, -0.2944087, 0.3084627, 0.0037425472, 0.9512291, 0.2956252, -0.95085174, -0.0921237, -3.629381, 10.86347, -2.0124264)
|
||||||
|
light_energy = 16.0
|
||||||
|
light_indirect_energy = 26.095
|
||||||
|
distance_fade_enabled = true
|
||||||
|
spot_range = 32.90586
|
||||||
|
spot_angle = 73.93
|
||||||
|
|
||||||
|
[node name="SUN_DirectionalLight3D2" type="DirectionalLight3D" parent="Lights" unique_id=1003843933]
|
||||||
|
transform = Transform3D(0.45811793, 0.34843865, -0.81775206, 0.5316611, 0.62986195, 0.56622505, 0.7123655, -0.69416475, 0.10329977, 0, 8.980043, 0)
|
||||||
|
light_energy = 0.106
|
||||||
|
shadow_enabled = true
|
||||||
|
shadow_blur = 10.0
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bm6rmt3xprhjs"
|
||||||
|
path="res://.godot/imported/bear_level_arena.glb-b6a9df3b338c8b0345fe2642d8b715eb.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/bear_level_arena.glb"
|
||||||
|
dest_files=["res://.godot/imported/bear_level_arena.glb-b6a9df3b338c8b0345fe2642d8b715eb.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
@@ -28,7 +28,7 @@ meshes/generate_lods=true
|
|||||||
meshes/create_shadow_meshes=true
|
meshes/create_shadow_meshes=true
|
||||||
meshes/light_baking=1
|
meshes/light_baking=1
|
||||||
meshes/lightmap_texel_size=0.2
|
meshes/lightmap_texel_size=0.2
|
||||||
meshes/force_disable_compression=false
|
meshes/force_disable_compression=true
|
||||||
skins/use_named_skins=true
|
skins/use_named_skins=true
|
||||||
animation/import=true
|
animation/import=true
|
||||||
animation/fps=30
|
animation/fps=30
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bdbx0wwa73rhp"
|
||||||
|
path="res://.godot/imported/Bear_FX.glb-5bac655e61b5e140f6cd145298d00ca8.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/fx/Bear_FX.glb"
|
||||||
|
dest_files=["res://.godot/imported/Bear_FX.glb-5bac655e61b5e140f6cd145298d00ca8.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://2e6fbder27w1"
|
||||||
|
path="res://.godot/imported/cubes_fx_test.glb-29490ce7011d666c1c4917a1a92679e4.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/fx/cubes_fx_test.glb"
|
||||||
|
dest_files=["res://.godot/imported/cubes_fx_test.glb-29490ce7011d666c1c4917a1a92679e4.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
|
After Width: | Height: | Size: 552 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dct1d88g015el"
|
||||||
|
path="res://.godot/imported/kleeps_v2.png-55aa0dd25620611263a8b3000c5e2a49.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/kleeps_v2.png"
|
||||||
|
dest_files=["res://.godot/imported/kleeps_v2.png-55aa0dd25620611263a8b3000c5e2a49.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 After Width: | Height: | Size: 3.5 MiB |
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bh5nuyudh7x44"
|
||||||
|
path="res://.godot/imported/loading_screen.png-253743b71eb7ab3f4f9e2b29cbabf407.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/loading_screen.png"
|
||||||
|
dest_files=["res://.godot/imported/loading_screen.png-253743b71eb7ab3f4f9e2b29cbabf407.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
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
$AnimationPlayer.play("Taunt")
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://bn3xwnlktwdlf
|
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
[gd_scene format=3 uid="uid://o8ocjfprxa7u"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://cn7fhjkro7637" path="res://bear.gd" id="1_beargd"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cofv5dbavesis" path="res://Assets/Bear.glb" id="2_bearglb"]
|
||||||
|
[ext_resource type="Script" uid="uid://bsbed05ifr18s" path="res://bear_test_cube.gd" id="3_rog1v"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bdbx0wwa73rhp" path="res://Assets/fx/Bear_FX.glb" id="4_rog1v"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
|
||||||
|
radius = 1.0
|
||||||
|
height = 2.4
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_hit"]
|
||||||
|
radius = 1.3
|
||||||
|
height = 2.7
|
||||||
|
|
||||||
|
[node name="Bear" type="CharacterBody3D" unique_id=1573237287]
|
||||||
|
script = ExtResource("1_beargd")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=830443382]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_body")
|
||||||
|
|
||||||
|
[node name="HitArea" type="Area3D" parent="." unique_id=365327397]
|
||||||
|
monitorable = false
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea" unique_id=2111867348]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_hit")
|
||||||
|
|
||||||
|
[node name="bear_model" parent="." unique_id=359217823 instance=ExtResource("2_bearglb")]
|
||||||
|
|
||||||
|
[node name="Skeleton3D" parent="bear_model/bear-rig" parent_id_path=PackedInt32Array(359217823, 1619621659) index="0" unique_id=1324818366]
|
||||||
|
bones/1/position = Vector3(0.04601016, 1.9209582, 0.9051311)
|
||||||
|
bones/1/rotation = Quaternion(0.5270001, -0.011489415, -0.05297156, 0.848135)
|
||||||
|
bones/2/rotation = Quaternion(0.29015693, 0.002787927, 0.00639222, 0.9569537)
|
||||||
|
bones/2/scale = Vector3(0.9999999, 1.0000008, 0.99999934)
|
||||||
|
bones/3/rotation = Quaternion(0.06707396, 0.012082217, 0.011868715, 0.9976043)
|
||||||
|
bones/4/rotation = Quaternion(0.3487565, 0.010761848, 0.013076123, 0.93706036)
|
||||||
|
bones/5/rotation = Quaternion(0.15472108, -0.21109715, -0.09227382, 0.96072114)
|
||||||
|
bones/6/rotation = Quaternion(0.4204113, 0.23492317, -0.11518617, 0.86879086)
|
||||||
|
bones/7/rotation = Quaternion(0.76830006, 0.36476925, -0.4668917, -0.24222025)
|
||||||
|
bones/8/rotation = Quaternion(0.5460264, -0.71257937, 0.40870878, -0.16444752)
|
||||||
|
bones/9/rotation = Quaternion(-0.02719195, -0.14156067, -0.26938534, 0.9521832)
|
||||||
|
bones/10/rotation = Quaternion(-0.12630412, -0.39785445, -0.057911687, 0.90686566)
|
||||||
|
bones/11/rotation = Quaternion(0.70209646, -0.35357565, 0.59454584, -0.16899753)
|
||||||
|
bones/12/rotation = Quaternion(0.5294657, 0.7712517, -0.33475664, -0.1130258)
|
||||||
|
bones/13/rotation = Quaternion(-0.04075809, 0.17025389, 0.28913936, 0.94114333)
|
||||||
|
bones/14/rotation = Quaternion(-0.03496992, 0.2568669, -0.0008229511, 0.9658136)
|
||||||
|
bones/15/rotation = Quaternion(0.9957146, -0.032195006, 0.031220285, 0.08087847)
|
||||||
|
bones/16/rotation = Quaternion(0.9367782, -0.012506716, 0.033659726, 0.3480767)
|
||||||
|
bones/17/rotation = Quaternion(-0.860235, -0.08192984, 0.009118035, 0.50319016)
|
||||||
|
bones/17/scale = Vector3(0.9999998, 1.0000002, 1)
|
||||||
|
bones/19/rotation = Quaternion(-4.514978e-15, 1, 7.4505806e-08, -5.523351e-07)
|
||||||
|
bones/20/rotation = Quaternion(0.9729887, -0.014750167, 0.014303936, 0.22993676)
|
||||||
|
bones/21/rotation = Quaternion(0.9366144, -0.00601367, 0.016095275, 0.34994036)
|
||||||
|
bones/22/rotation = Quaternion(-0.7732739, -0.06955802, 0.0075612683, 0.6302)
|
||||||
|
bones/22/scale = Vector3(0.9999997, 0.9999998, 0.9999999)
|
||||||
|
bones/24/rotation = Quaternion(6.628114e-12, 0.99999994, 5.2154085e-08, -0.00035543842)
|
||||||
|
bones/25/position = Vector3(1.0117401, 1.3159828, 3.9725027)
|
||||||
|
bones/25/rotation = Quaternion(-0.27285534, 0.9274522, 0.08017836, 0.2428039)
|
||||||
|
bones/25/scale = Vector3(1.0000001, 12.140265, 1.0000001)
|
||||||
|
bones/26/position = Vector3(-0.9025072, 1.4953425, 4.006362)
|
||||||
|
bones/26/rotation = Quaternion(0.5100001, 0.6936306, 0.15438241, -0.48470873)
|
||||||
|
bones/26/scale = Vector3(0.99999994, 11.348344, 1)
|
||||||
|
bones/27/position = Vector3(0.70899034, 1.0356265, 0.611243)
|
||||||
|
bones/27/rotation = Quaternion(0.7965308, 0.01196337, -0.0119632045, 0.6043613)
|
||||||
|
bones/27/scale = Vector3(0.99999994, 29.328602, 1)
|
||||||
|
bones/28/position = Vector3(-0.9047001, 1.0118608, 0.8510791)
|
||||||
|
bones/28/rotation = Quaternion(0.776662, 0.010376095, -0.010376263, 0.6297467)
|
||||||
|
bones/28/scale = Vector3(1, 26.535925, 1)
|
||||||
|
bones/30/position = Vector3(0.07262612, 0.67294323, -1.1644177)
|
||||||
|
bones/30/rotation = Quaternion(-0.7183768, 0.04937226, -0.02237074, 0.69353926)
|
||||||
|
bones/31/position = Vector3(5.9604645e-08, -1.4631644, 0.23291233)
|
||||||
|
bones/31/rotation = Quaternion(1.1641533e-09, 1.8335415e-09, 2.328307e-10, 1)
|
||||||
|
bones/31/scale = Vector3(0.99999994, 0.9999999, 0.99999994)
|
||||||
|
bones/32/position = Vector3(-0.0331195, -2.362161, -0.036310136)
|
||||||
|
bones/32/rotation = Quaternion(0.20497073, -0.020519394, -0.033738203, 0.9779712)
|
||||||
|
bones/32/scale = Vector3(0.9999999, 0.9999999, 0.99999994)
|
||||||
|
bones/33/position = Vector3(8.195639e-08, -1.4631642, 0.23291235)
|
||||||
|
bones/33/rotation = Quaternion(-8.288772e-08, 3.4342527e-09, -4.1909525e-09, 1)
|
||||||
|
bones/33/scale = Vector3(0.9999998, 0.9999998, 0.99999994)
|
||||||
|
bones/35/position = Vector3(-7.893884e-09, -1.0594703, 5.2079617e-08)
|
||||||
|
bones/35/rotation = Quaternion(0.99971783, 1.2737803e-07, 5.365157e-06, -0.023754692)
|
||||||
|
bones/35/scale = Vector3(0.99999976, 0.9999998, 1.0000012)
|
||||||
|
bones/39/position = Vector3(0.8199172, 0.25251278, 0.47214246)
|
||||||
|
bones/39/rotation = Quaternion(0.9961935, -0.035078514, -0.0045541152, 0.07967066)
|
||||||
|
bones/39/scale = Vector3(1.0000004, 0.9999996, 0.9999991)
|
||||||
|
bones/40/rotation = Quaternion(0.93738276, 1.2575202e-07, 3.4991626e-08, 0.3483009)
|
||||||
|
bones/40/scale = Vector3(1.0000004, 1.0000001, 1.0000001)
|
||||||
|
bones/41/rotation = Quaternion(-0.860235, -0.081929855, 0.009117997, 0.5031899)
|
||||||
|
bones/41/scale = Vector3(0.99999946, 0.99999976, 0.9999996)
|
||||||
|
bones/42/position = Vector3(-1.9729964e-07, 0.56145483, 7.283501e-08)
|
||||||
|
bones/46/scale = Vector3(0.99999946, 0.99999964, 0.9999999)
|
||||||
|
bones/48/rotation = Quaternion(9.8842406e-08, -0.054423165, -1.786034e-08, 0.998518)
|
||||||
|
bones/48/scale = Vector3(0.9999993, 0.9999996, 0.9999999)
|
||||||
|
bones/50/scale = Vector3(0.99999994, 0.99999994, 0.9999998)
|
||||||
|
bones/51/rotation = Quaternion(-6.30798e-08, 0.026635498, 6.253992e-09, 0.99964523)
|
||||||
|
bones/51/scale = Vector3(0.9999999, 0.99999964, 0.99999964)
|
||||||
|
bones/53/position = Vector3(-0.8199191, 0.25251284, 0.47213903)
|
||||||
|
bones/53/rotation = Quaternion(0.9730908, -0.0186988, -0.0024160824, 0.22964936)
|
||||||
|
bones/53/scale = Vector3(1, 0.99999875, 0.99999946)
|
||||||
|
bones/54/rotation = Quaternion(0.9367526, -1.1281111e-07, -3.6940495e-08, 0.34999225)
|
||||||
|
bones/55/rotation = Quaternion(-0.77327377, -0.06955799, 0.007561261, 0.6302002)
|
||||||
|
bones/55/scale = Vector3(1.0000002, 1, 1.0000002)
|
||||||
|
bones/56/position = Vector3(-5.9126133e-08, 0.5614537, 7.70865e-07)
|
||||||
|
bones/57/rotation = Quaternion(0.70073944, -0.70073956, -0.094679825, -0.09467953)
|
||||||
|
bones/60/scale = Vector3(1.0000004, 1.0000004, 1.0000002)
|
||||||
|
bones/62/rotation = Quaternion(5.8764627e-08, -0.042629674, 2.4442278e-08, 0.99909097)
|
||||||
|
bones/62/scale = Vector3(1.0000002, 1.0000002, 1.0000001)
|
||||||
|
bones/64/scale = Vector3(1.0000004, 1.0000004, 1)
|
||||||
|
bones/65/rotation = Quaternion(8.029546e-08, 0.01272784, 4.3857337e-08, 0.999919)
|
||||||
|
bones/65/scale = Vector3(1.0000002, 1.0000004, 1.0000004)
|
||||||
|
bones/67/rotation = Quaternion(0.7179022, -7.671307e-07, 7.885642e-07, 0.696144)
|
||||||
|
bones/67/scale = Vector3(1.0000004, 0.9999986, 0.99999994)
|
||||||
|
bones/68/rotation = Quaternion(0.9776482, 2.0029378e-07, 1.0460874e-06, -0.2102479)
|
||||||
|
bones/68/scale = Vector3(1.0000004, 1.0000004, 0.9999982)
|
||||||
|
bones/69/position = Vector3(-2.4645832e-08, -6.861796e-09, 7.455772e-08)
|
||||||
|
bones/69/rotation = Quaternion(-0.8315212, 2.5276703e-08, -9.3330055e-09, 0.5554931)
|
||||||
|
bones/69/scale = Vector3(1, 1.0000001, 1.0000002)
|
||||||
|
bones/70/rotation = Quaternion(0.67750055, 8.820757e-08, 7.575834e-08, 0.7355223)
|
||||||
|
bones/70/scale = Vector3(1, 1, 1)
|
||||||
|
bones/71/scale = Vector3(0.9999999, 1, 0.9999999)
|
||||||
|
bones/72/rotation = Quaternion(-2.1641608e-07, -4.6565607e-10, -4.6576065e-10, 1)
|
||||||
|
bones/75/rotation = Quaternion(0.25299338, 0.020847347, 0.028576687, 0.9668212)
|
||||||
|
bones/76/position = Vector3(1.01587794e-07, -1.5306958e-07, -4.3554866e-07)
|
||||||
|
bones/76/rotation = Quaternion(-0.5206146, -3.634943e-08, 7.6474e-08, 0.8537919)
|
||||||
|
bones/77/rotation = Quaternion(0.9373828, 1.2785249e-07, 3.3241214e-08, 0.3483008)
|
||||||
|
bones/78/position = Vector3(0.8199172, 0.25251257, 0.47214243)
|
||||||
|
bones/78/rotation = Quaternion(0.99571455, -0.032195, 0.031220287, 0.08087897)
|
||||||
|
bones/78/scale = Vector3(1.0000004, 0.9999997, 0.99999905)
|
||||||
|
bones/80/rotation = Quaternion(0.7179022, -7.6713064e-07, 7.885641e-07, 0.696144)
|
||||||
|
bones/80/scale = Vector3(1.0000004, 0.9999986, 0.99999994)
|
||||||
|
bones/81/rotation = Quaternion(0.97764814, 1.9846473e-07, 1.0426893e-06, -0.21024783)
|
||||||
|
bones/81/scale = Vector3(1.0000004, 1.0000002, 0.9999982)
|
||||||
|
bones/82/position = Vector3(6.103571e-08, 8.8816435e-08, -3.6955274e-09)
|
||||||
|
bones/82/rotation = Quaternion(-0.8315211, 2.3407136e-08, -1.2821512e-08, 0.5554932)
|
||||||
|
bones/82/scale = Vector3(0.99999994, 0.99999994, 1)
|
||||||
|
bones/83/rotation = Quaternion(0.6775009, -8.600083e-08, -8.977273e-08, 0.735522)
|
||||||
|
bones/83/scale = Vector3(1, 1.0000001, 1)
|
||||||
|
bones/84/scale = Vector3(0.99999994, 1.0000001, 0.9999999)
|
||||||
|
bones/85/rotation = Quaternion(2.6833732e-07, -9.313788e-10, -2.5612494e-09, 1)
|
||||||
|
bones/85/scale = Vector3(1.0000001, 1, 1.0000001)
|
||||||
|
bones/87/rotation = Quaternion(-0.5230753, 0.20132942, 0.026226493, 0.8277505)
|
||||||
|
bones/88/rotation = Quaternion(0.10320552, 0.011111915, 0.015231778, 0.99448144)
|
||||||
|
bones/89/position = Vector3(2.953037e-07, 4.9207458e-08, -4.3015167e-08)
|
||||||
|
bones/89/rotation = Quaternion(-0.5201237, 5.177172e-08, -1.16139375e-07, 0.8540909)
|
||||||
|
bones/90/rotation = Quaternion(0.9367527, -1.0975643e-07, -3.3382268e-08, 0.34999216)
|
||||||
|
bones/91/position = Vector3(-0.8199192, 0.2525128, 0.47213912)
|
||||||
|
bones/91/rotation = Quaternion(0.9729887, -0.0147501845, 0.014303951, 0.22993667)
|
||||||
|
bones/91/scale = Vector3(1.0000002, 0.999999, 0.99999964)
|
||||||
|
bones/96/position = Vector3(-0.016559705, -1.9126627, 0.09830098)
|
||||||
|
bones/96/rotation = Quaternion(0.10305436, -0.010316663, -0.016962789, 0.99447757)
|
||||||
|
bones/96/scale = Vector3(0.9999998, 1, 1)
|
||||||
|
bones/97/rotation = Quaternion(0.997074, -1.2813135e-07, 1.666339e-06, -0.07644201)
|
||||||
|
bones/98/position = Vector3(0.0013585633, -0.23419222, 0.016214281)
|
||||||
|
bones/98/rotation = Quaternion(-0.15130657, -0.005602528, -0.008209973, 0.98843694)
|
||||||
|
bones/98/scale = Vector3(1.0000001, 1.0000006, 1.0000029)
|
||||||
|
bones/101/position = Vector3(-0.016559973, 1.4684486, 0.06451648)
|
||||||
|
bones/101/rotation = Quaternion(0.99944556, 0.016124567, -0.011581497, -0.02673253)
|
||||||
|
bones/101/scale = Vector3(1.0000001, 0.9999993, 0.99999183)
|
||||||
|
bones/102/rotation = Quaternion(0.95545256, -6.924111e-08, 2.2887033e-07, -0.29514462)
|
||||||
|
bones/103/position = Vector3(-0.00705543, 0.6694702, -0.06780842)
|
||||||
|
bones/103/rotation = Quaternion(0.15471841, -0.21109664, -0.09227309, 0.9607217)
|
||||||
|
bones/103/scale = Vector3(1.0000005, 1.0000286, 1.0000087)
|
||||||
|
bones/104/rotation = Quaternion(0.420411, 0.23492327, -0.115186565, 0.86879104)
|
||||||
|
bones/104/scale = Vector3(0.9999993, 0.9999884, 0.9999856)
|
||||||
|
bones/105/rotation = Quaternion(0.91976035, -0.013162326, 0.0148637965, 0.39197809)
|
||||||
|
bones/105/scale = Vector3(1, 1.0000005, 1.0000114)
|
||||||
|
bones/106/position = Vector3(-0.010389687, -0.091948025, 0.0016658108)
|
||||||
|
bones/106/rotation = Quaternion(0.82734275, 0.15726796, -0.14167279, -0.52028793)
|
||||||
|
bones/107/rotation = Quaternion(0.77328545, 0.16233337, -0.107321426, 0.6034564)
|
||||||
|
bones/107/scale = Vector3(0.9999966, 0.99997723, 0.9999995)
|
||||||
|
bones/108/position = Vector3(-1.1340098e-08, 7.923448e-07, 9.0939814e-08)
|
||||||
|
bones/108/rotation = Quaternion(0.42463258, 0.043219328, 0.08968367, -0.89987564)
|
||||||
|
bones/110/rotation = Quaternion(5.443812e-06, 3.6259098e-09, -1.2069942e-06, 1)
|
||||||
|
bones/110/scale = Vector3(0.99999994, 0.99999976, 1.0000001)
|
||||||
|
bones/113/position = Vector3(0.08179821, 0.64012206, 0.7106584)
|
||||||
|
bones/113/rotation = Quaternion(0.7683008, 0.36476624, -0.46689337, -0.24221936)
|
||||||
|
bones/114/position = Vector3(0.08595797, 0.6463289, -0.32087424)
|
||||||
|
bones/114/rotation = Quaternion(0.5615046, -0.7056476, 0.3871702, -0.19202453)
|
||||||
|
bones/114/scale = Vector3(1.0000049, 1.0000027, 1.0000052)
|
||||||
|
bones/115/rotation = Quaternion(-0.037640397, -0.10443457, -0.2681242, 0.9569672)
|
||||||
|
bones/115/scale = Vector3(0.9999974, 1.0000036, 0.99999976)
|
||||||
|
bones/116/rotation = Quaternion(-0.12630361, -0.39785278, -0.057911348, 0.90686655)
|
||||||
|
bones/116/scale = Vector3(1.0000005, 0.9999972, 1.0000013)
|
||||||
|
bones/119/scale = Vector3(1.0000043, 0.9999975, 0.99999744)
|
||||||
|
bones/121/rotation = Quaternion(-2.7027176e-07, -0.20424414, 2.9341666e-07, 0.97892)
|
||||||
|
bones/121/scale = Vector3(1.0000037, 0.99999774, 0.9999977)
|
||||||
|
bones/123/scale = Vector3(1.000003, 0.9999977, 0.99999917)
|
||||||
|
bones/124/rotation = Quaternion(1.2542905e-06, -0.033494003, 5.941339e-07, 0.999439)
|
||||||
|
bones/124/scale = Vector3(1.0000037, 0.9999978, 0.99999875)
|
||||||
|
bones/126/rotation = Quaternion(-0.51635414, -0.1681693, 0.6340872, 0.5504825)
|
||||||
|
bones/126/scale = Vector3(1.0000056, 1.0000031, 1.0000038)
|
||||||
|
bones/127/rotation = Quaternion(-0.24332374, 0.12663902, 0.6612198, 0.6982439)
|
||||||
|
bones/127/scale = Vector3(1.0000057, 1.0000007, 1.0000064)
|
||||||
|
bones/128/position = Vector3(3.0230987e-07, -5.5011895e-07, -6.94335e-08)
|
||||||
|
bones/128/rotation = Quaternion(0.17027588, 0.93388194, -0.2967173, 0.104064964)
|
||||||
|
bones/129/rotation = Quaternion(-0.037640374, -0.10443452, -0.26812422, 0.9569671)
|
||||||
|
bones/131/rotation = Quaternion(-0.54023695, -0.3639527, 0.10910668, 0.7508517)
|
||||||
|
bones/132/rotation = Quaternion(-0.13163939, 0.9186296, -0.34996635, -0.12772812)
|
||||||
|
bones/133/rotation = Quaternion(-0.012778445, 0.011854936, 0.00015161023, 0.99984807)
|
||||||
|
bones/133/scale = Vector3(1.0422997, 1.0422999, 1.0422992)
|
||||||
|
bones/134/rotation = Quaternion(0.02152282, -2.7759576e-08, -1.5735115e-07, 0.99976844)
|
||||||
|
bones/134/scale = Vector3(0.98826313, 0.9882641, 0.9882627)
|
||||||
|
bones/135/position = Vector3(0.08595812, 0.646329, -0.3208742)
|
||||||
|
bones/135/rotation = Quaternion(0.5460276, -0.7125794, 0.40870702, -0.16444758)
|
||||||
|
bones/135/scale = Vector3(1.000007, 1.0000004, 1.0000051)
|
||||||
|
bones/137/position = Vector3(-0.09762837, 0.6401221, 0.7106583)
|
||||||
|
bones/137/rotation = Quaternion(0.7020965, -0.35357338, 0.5945473, -0.16899663)
|
||||||
|
bones/138/position = Vector3(-0.08595776, 0.646329, -0.3208746)
|
||||||
|
bones/138/rotation = Quaternion(0.5492876, 0.7627154, -0.30113, -0.16083841)
|
||||||
|
bones/138/scale = Vector3(1.0000051, 1.0000018, 1.0000046)
|
||||||
|
bones/139/rotation = Quaternion(-0.058682628, 0.11130926, 0.28603986, 0.94991994)
|
||||||
|
bones/139/scale = Vector3(0.9999983, 1.0000024, 0.9999992)
|
||||||
|
bones/140/rotation = Quaternion(-0.034969915, 0.25686622, -0.0008231461, 0.96581376)
|
||||||
|
bones/140/scale = Vector3(1.0000031, 0.9999977, 0.9999988)
|
||||||
|
bones/143/scale = Vector3(1.000005, 0.99999815, 0.9999967)
|
||||||
|
bones/145/rotation = Quaternion(-4.0431777e-07, 0.1295653, -5.1404834e-07, 0.99157095)
|
||||||
|
bones/145/scale = Vector3(1.0000045, 0.99999803, 0.9999968)
|
||||||
|
bones/147/scale = Vector3(1.0000035, 0.99999845, 0.9999981)
|
||||||
|
bones/148/rotation = Quaternion(1.9343124e-06, 0.025161995, -3.222267e-07, 0.9996834)
|
||||||
|
bones/148/scale = Vector3(1.000004, 0.9999991, 0.999998)
|
||||||
|
bones/150/rotation = Quaternion(-0.5163542, 0.16816999, -0.6340877, 0.5504816)
|
||||||
|
bones/150/scale = Vector3(1.0000052, 1.0000039, 1.0000025)
|
||||||
|
bones/151/rotation = Quaternion(-0.24332343, -0.1266389, -0.6612192, 0.6982446)
|
||||||
|
bones/151/scale = Vector3(1.0000049, 1.0000005, 1.0000063)
|
||||||
|
bones/152/position = Vector3(-2.0525843e-07, -4.4378166e-07, 2.2242949e-07)
|
||||||
|
bones/152/rotation = Quaternion(-0.1980554, 0.9486651, -0.20058647, -0.14343536)
|
||||||
|
bones/153/rotation = Quaternion(-0.058682676, 0.11130926, 0.2860399, 0.94991994)
|
||||||
|
bones/155/rotation = Quaternion(-0.48170745, 0.21343125, -0.09362694, 0.84477156)
|
||||||
|
bones/155/scale = Vector3(0.99999994, 1, 0.9999998)
|
||||||
|
bones/156/rotation = Quaternion(0.2123156, 0.9450362, -0.15822437, 0.19181721)
|
||||||
|
bones/157/rotation = Quaternion(-0.01277751, -0.011854968, -0.0001513001, 0.9998481)
|
||||||
|
bones/157/scale = Vector3(1.0423003, 1.0422994, 1.0423005)
|
||||||
|
bones/158/rotation = Quaternion(0.021521209, -2.5687226e-07, -2.570953e-07, 0.9997684)
|
||||||
|
bones/158/scale = Vector3(0.9882631, 0.98826426, 0.9882618)
|
||||||
|
bones/159/position = Vector3(-0.085957706, 0.6463291, -0.32087478)
|
||||||
|
bones/159/rotation = Quaternion(0.5294669, 0.7712517, -0.33475488, -0.1130262)
|
||||||
|
bones/159/scale = Vector3(1.0000075, 1.0000001, 1.0000042)
|
||||||
|
bones/161/position = Vector3(0.08179821, 0.64012206, 0.7106584)
|
||||||
|
bones/161/rotation = Quaternion(0.7683008, 0.36476624, -0.46689337, -0.24221936)
|
||||||
|
bones/162/position = Vector3(-0.09762837, 0.6401221, 0.7106583)
|
||||||
|
bones/162/rotation = Quaternion(0.7020965, -0.35357338, 0.5945473, -0.16899663)
|
||||||
|
bones/165/position = Vector3(-1.5017577e-08, 0.26895812, -1.6243575e-08)
|
||||||
|
bones/165/rotation = Quaternion(-0.019247467, -0.20323175, 0.97894067, -0.0012537595)
|
||||||
|
bones/166/position = Vector3(0.016703974, -2.2814677, 2.0323193)
|
||||||
|
bones/167/position = Vector3(0.7698192, 2.760036, -1.7026894)
|
||||||
|
bones/167/rotation = Quaternion(-0.93479383, -0.013274327, 0.31412983, 0.16524744)
|
||||||
|
bones/167/scale = Vector3(0.9999947, 0.9999997, 1.0000055)
|
||||||
|
bones/168/position = Vector3(0.18381567, -0.9622623, 0.9894084)
|
||||||
|
bones/168/rotation = Quaternion(0.93622446, 1.3019375e-07, -0.34127164, -0.08377019)
|
||||||
|
bones/168/scale = Vector3(1.0000024, 1.0000011, 1.0000095)
|
||||||
|
bones/170/position = Vector3(0.748137, 0.69556385, 3.7252903e-09)
|
||||||
|
bones/170/rotation = Quaternion(0.019250846, -0.20323181, 0.9789406, 0.001253055)
|
||||||
|
bones/171/position = Vector3(0.381392, -2.057497, 2.0762825)
|
||||||
|
bones/172/position = Vector3(-0.9278099, 2.7545004, -1.5181485)
|
||||||
|
bones/172/rotation = Quaternion(-0.91823035, 0.06278634, -0.38804358, 0.04830265)
|
||||||
|
bones/172/scale = Vector3(0.99999493, 0.9999995, 1.0000055)
|
||||||
|
bones/173/position = Vector3(0.18381546, -0.96226203, 0.9894083)
|
||||||
|
bones/173/rotation = Quaternion(0.9362244, 3.755922e-07, 0.3412715, -0.0837717)
|
||||||
|
bones/173/scale = Vector3(1.0000001, 1.0000001, 0.9999949)
|
||||||
|
bones/183/position = Vector3(-1.982537e-07, 5.9604563e-08, -1.517833e-08)
|
||||||
|
bones/185/rotation = Quaternion(5.4077844e-07, -0.20349081, 0.97907686, -1.12395135e-07)
|
||||||
|
bones/186/position = Vector3(0.78680634, 2.8457582, -0.41063747)
|
||||||
|
bones/186/rotation = Quaternion(-0.9983755, 0.019092962, -0.050729975, -0.017562928)
|
||||||
|
bones/186/scale = Vector3(0.9999997, 0.99999994, 1)
|
||||||
|
bones/187/rotation = Quaternion(0.15451065, -2.0305349e-09, -2.0850066e-09, 0.9879911)
|
||||||
|
bones/187/scale = Vector3(1.0000002, 0.99999994, 0.9999999)
|
||||||
|
bones/197/position = Vector3(7.903883e-08, 1.4901151e-07, 8.8932055e-08)
|
||||||
|
bones/199/rotation = Quaternion(0.00034800146, -0.20349097, 0.9790768, -7.2328476e-05)
|
||||||
|
bones/200/position = Vector3(-0.84339374, 2.905353, -0.57784474)
|
||||||
|
bones/200/rotation = Quaternion(-0.9983755, 0.019092962, -0.050729975, -0.017562928)
|
||||||
|
bones/200/scale = Vector3(0.9999997, 0.99999994, 1)
|
||||||
|
bones/201/rotation = Quaternion(0.15451066, -1.9585848e-09, -2.5450761e-09, 0.9879911)
|
||||||
|
bones/201/scale = Vector3(1.0000002, 0.99999994, 0.99999994)
|
||||||
|
|
||||||
|
[node name="test_cube" type="Node3D" parent="." unique_id=646988893]
|
||||||
|
script = ExtResource("3_rog1v")
|
||||||
|
|
||||||
|
[node name="Bear_FX" parent="test_cube" unique_id=1183391245 instance=ExtResource("4_rog1v")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.1459732, 0, -1.528423)
|
||||||
|
|
||||||
|
[node name="Bear_fx_claw_attack_R" parent="test_cube/Bear_FX" index="1" unique_id=941189652]
|
||||||
|
transform = Transform3D(0.26165774, 0.06970292, -0.23443498, -0.046572562, 0.18028688, 0.0016228362, 0.22878301, 0.056650188, 0.2721929, -0.40204993, 0.29855162, 0.917461)
|
||||||
|
|
||||||
|
[editable path="bear_model"]
|
||||||
|
[editable path="test_cube/Bear_FX"]
|
||||||
@@ -12,13 +12,25 @@ I am a **Godot 4.6+ expert**. I follow current best practices for GDScript, scen
|
|||||||
|
|
||||||
| Path | Purpose |
|
| Path | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `scene.tscn` | Main scene (arena + player) |
|
| `scene.tscn` | The reusable **shell** loaded for every level: player, camera, HUD, PS1 filter, and an empty `LevelRoot`. Its `game_shell.gd` instances the active level's module into `LevelRoot` at runtime |
|
||||||
|
| `levels/` | The level system: per-level module scenes plus the code that defines, loads and sequences them (see rows below) |
|
||||||
|
| `levels/game_shell.gd` | Shell controller — on load, frees any current level module and instances `Run.active_level().level_scene` into `LevelRoot`. Swapping the whole subtree (never hiding it) is what keeps one level's meshes/colliders/spawners from leaking into the next |
|
||||||
|
| `levels/arena_level.tscn` | The arena **level module**: floor + wall colliders, arena meshes, `MatadorSpawner` (with child `Node3D` spawn points placed by the designer), lighting, audience (`PublikNode`). Instanced into the shell's `LevelRoot` |
|
||||||
|
| `levels/bear_level.tscn` | The Bear's Den **level module**: bear-arena mesh, its own floor collider, lighting, and a `BearSpawner` (`bear_spawn.gd`) that places the bear at center. Instanced into the shell's `LevelRoot` |
|
||||||
| `Player.tscn` | Player scene root (`CharacterBody3D`) |
|
| `Player.tscn` | Player scene root (`CharacterBody3D`) |
|
||||||
| `player.gd` | Movement, charge, turning logic |
|
| `player.gd` | Movement, charge, turning logic |
|
||||||
| `camera_spring_arm.gd` | Mouse-look pivot (`Node3D` + `SpringArm3D`) |
|
| `camera_spring_arm.gd` | Mouse-look pivot (`Node3D` + `SpringArm3D`) |
|
||||||
| `camera_follow.gd` | Smooth camera follow (`Camera3D`) |
|
| `camera_follow.gd` | Smooth camera follow (`Camera3D`) |
|
||||||
| `matador.gd` | Matador AI (wander / ragdoll state machine) |
|
| `matador.gd` | Matador AI (wander / ragdoll state machine) |
|
||||||
| `matador_spawn.gd` | Spawns N matadors at random arena positions |
|
| `levels/matador_spawn.gd` | Arena spawner — one matador per designer-placed child `Node3D` spawn point (each enters with a jump-roll toward center); falls back to a programmatic spread if no spawn points are present. Also handles the split-on-death hydra logic |
|
||||||
|
| `levels/bear_spawn.gd` | Bear-level spawner — instantiates `enemy_scene` at the arena center; emits the same `all_defeated` / `matador_killed` signals as `matador_spawn.gd` |
|
||||||
|
| `Bear.tscn` / `bear.gd` | Dark-Souls-style bear boss (`CharacterBody3D`). Routine state machine (wind-up → active → recovery) picking telegraphed moves by range band: Double Swipe / Overhead Smash (close), Leap Slam (mid-far), plus Stalk (2-leg walk-in) / Prowl (4-leg lope) positioning. Melee steps in during the swing (`bear_lunge_speed`) + tracks the bull; the leap is frame-synced to JUMP_SMASH so the paws hit ground on the impact frame (`bear_leap_impact_frame`); slams kick up dust + shake. Combos chain, hyper-armour mid-swing + poise (`bear_stagger_cd`), phase 2 enrage under `bear_enrage_frac` HP. Never damages the bull by colliding — only swings/slams. Joins `&"matador"` so bull abilities already hit it. Tuning under the `Bear` DP section |
|
||||||
|
| `levels/run_state.gd` | Run progression autoload (`Run`) — Slay-the-Spire map + player position across fights |
|
||||||
|
| `levels/level_def.gd` | `LevelDef` resource — a level's name / map colour / `scene_path` (always the shell) / `level_scene` (the level module instanced into the shell) / optional `enemy_scene` (arena wave vs. a boss like the bear) |
|
||||||
|
| `levels/map_node.gd` | `MapNode` — one runtime node on the run map (position, level, links) |
|
||||||
|
| `levels/MapScreen.tscn` / `levels/map_screen.gd` | Between-fights map screen; win → pick a node → next level |
|
||||||
|
| `crowd_marker.gd` | `CrowdMarker` (`@tool Node3D`, `class_name`) — designer drops one per billboard spectator via the editor's Add-Node dialog; picks one of six 60°-spaced facings from the `orientation` dropdown. Draws an editor-only preview (slab + forward arrow, never serialised) and joins the `&"crowd_marker"` group |
|
||||||
|
| `crowd_billboards.gd` / `crowd_billboard.gdshader` | `MultiMeshInstance3D` that harvests every `CrowdMarker` (group `&"crowd_marker"`) into one draw call, using each marker's world position + chosen facing. Front/back sprite sheet baked by `tools/bake_crowd_sheet.gd`; falls back to a generated inward-facing ring when no markers are placed |
|
||||||
| `debug_params.gd` | Runtime-tunable parameter registry (autoload `DP`) |
|
| `debug_params.gd` | Runtime-tunable parameter registry (autoload `DP`) |
|
||||||
| `Assets/` | Raw 3D assets (`.glb`, `.fbx`) |
|
| `Assets/` | Raw 3D assets (`.glb`, `.fbx`) |
|
||||||
| `Blender/` | Blender source files, animation scripts, FBX exports |
|
| `Blender/` | Blender source files, animation scripts, FBX exports |
|
||||||
@@ -88,10 +100,12 @@ After FBX import, animations appear as `Armature|walk` and `Armature|idle` in An
|
|||||||
bash run_tests.sh
|
bash run_tests.sh
|
||||||
|
|
||||||
# Individual tests:
|
# Individual tests:
|
||||||
gdlint *.gd tests/*.gd # GDScript lint (gdtoolkit, installed via uv)
|
gdlint *.gd levels/*.gd tests/*.gd # GDScript lint (gdtoolkit, installed via uv)
|
||||||
godot --headless --script tests/logic_test.gd # pure logic, ~2 s
|
godot --headless --script tests/logic_test.gd # pure logic, ~2 s
|
||||||
godot --headless --script tests/performance_test.gd # frame-budget regression guard, ~4 s
|
godot --headless --script tests/performance_test.gd # frame-budget regression guard, ~4 s
|
||||||
godot --headless --script tests/gameplay_test.gd # full scene, bone sanity, ~4 s
|
godot --headless --script tests/gameplay_test.gd # full scene, bone sanity, ~4 s
|
||||||
|
godot --headless --script tests/level_switch_test.gd # no arena geometry leaks into the bear level, ~2 s
|
||||||
|
godot --headless --script tests/bear_boss_test.gd # bear boss moveset: routines, leap landing, hyper-armour, ~10 s
|
||||||
|
|
||||||
# Gameplay test with real rendering (inspect frame strip visually):
|
# Gameplay test with real rendering (inspect frame strip visually):
|
||||||
godot --script tests/gameplay_test.gd # opens a window, saves real screenshots
|
godot --script tests/gameplay_test.gd # opens a window, saves real screenshots
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ importer="texture"
|
|||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://bxm7c6ldrdiq5"
|
uid="uid://bxm7c6ldrdiq5"
|
||||||
path.bptc="res://.godot/imported/citrus_orchard_road_puresky_4k.hdr-262868e8787abf05f7044da72beefce7.bptc.ctex"
|
path.bptc="res://.godot/imported/citrus_orchard_road_puresky_4k.hdr-262868e8787abf05f7044da72beefce7.bptc.ctex"
|
||||||
|
path.astc="res://.godot/imported/citrus_orchard_road_puresky_4k.hdr-262868e8787abf05f7044da72beefce7.astc.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"imported_formats": ["s3tc_bptc"],
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
"vram_texture": true
|
"vram_texture": true
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://HDRI/citrus_orchard_road_puresky_4k.hdr"
|
source_file="res://HDRI/citrus_orchard_road_puresky_4k.hdr"
|
||||||
dest_files=["res://.godot/imported/citrus_orchard_road_puresky_4k.hdr-262868e8787abf05f7044da72beefce7.bptc.ctex"]
|
dest_files=["res://.godot/imported/citrus_orchard_road_puresky_4k.hdr-262868e8787abf05f7044da72beefce7.bptc.ctex", "res://.godot/imported/citrus_orchard_road_puresky_4k.hdr-262868e8787abf05f7044da72beefce7.astc.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ importer="texture"
|
|||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://3syhacswepyj"
|
uid="uid://3syhacswepyj"
|
||||||
path.bptc="res://.godot/imported/kloppenheim_06_puresky_4k.hdr-026f0eabdfce50035e2f8417ce3e0060.bptc.ctex"
|
path.bptc="res://.godot/imported/kloppenheim_06_puresky_4k.hdr-026f0eabdfce50035e2f8417ce3e0060.bptc.ctex"
|
||||||
|
path.astc="res://.godot/imported/kloppenheim_06_puresky_4k.hdr-026f0eabdfce50035e2f8417ce3e0060.astc.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"imported_formats": ["s3tc_bptc"],
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
"vram_texture": true
|
"vram_texture": true
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://HDRI/kloppenheim_06_puresky_4k.hdr"
|
source_file="res://HDRI/kloppenheim_06_puresky_4k.hdr"
|
||||||
dest_files=["res://.godot/imported/kloppenheim_06_puresky_4k.hdr-026f0eabdfce50035e2f8417ce3e0060.bptc.ctex"]
|
dest_files=["res://.godot/imported/kloppenheim_06_puresky_4k.hdr-026f0eabdfce50035e2f8417ce3e0060.bptc.ctex", "res://.godot/imported/kloppenheim_06_puresky_4k.hdr-026f0eabdfce50035e2f8417ce3e0060.astc.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ dof_blur_near_transition = 5.0
|
|||||||
transform = Transform3D(-0.56715536, 0, -0.8236109, 0, 1, 0, 0.8236109, 0, -0.56715536, 5.2114463, -0.08998108, 1.8927763)
|
transform = Transform3D(-0.56715536, 0, -0.8236109, 0, 1, 0, 0.8236109, 0, -0.56715536, 5.2114463, -0.08998108, 1.8927763)
|
||||||
script = ExtResource("2_5jr0i")
|
script = ExtResource("2_5jr0i")
|
||||||
|
|
||||||
[node name="Skeleton3D" parent="Matador/Armature" parent_id_path=PackedInt32Array(166852612, 1269629432) index="0" unique_id=978366810]
|
[node name="Skeleton3D" parent="Matador/Armature" parent_id_path=PackedInt32Array(166852612, 1447304122) index="0" unique_id=357848305]
|
||||||
bones/0/position = Vector3(0.056660548, 0.6424767, -0.0167365)
|
bones/0/position = Vector3(0.056660548, 0.6424767, -0.0167365)
|
||||||
bones/0/rotation = Quaternion(0.013179442, -0.2660563, 0.073319815, 0.9610747)
|
bones/0/rotation = Quaternion(0.013179442, -0.2660563, 0.073319815, 0.9610747)
|
||||||
bones/1/rotation = Quaternion(0.06234612, 0.049236443, 0.08183311, 0.99347484)
|
bones/1/rotation = Quaternion(0.06234612, 0.049236443, 0.08183311, 0.99347484)
|
||||||
@@ -81,10 +81,10 @@ bones/23/rotation = Quaternion(0.9993525, -0.0021175682, 0.03502641, -0.00795782
|
|||||||
bones/24/rotation = Quaternion(0.08874816, -0.018719967, -0.04671622, 0.99478185)
|
bones/24/rotation = Quaternion(0.08874816, -0.018719967, -0.04671622, 0.99478185)
|
||||||
bones/25/rotation = Quaternion(0.09634846, -0.0268489, -0.04050248, 0.9941608)
|
bones/25/rotation = Quaternion(0.09634846, -0.0268489, -0.04050248, 0.9941608)
|
||||||
|
|
||||||
[node name="matador" parent="Matador/Armature/Skeleton3D" index="0" unique_id=256196413]
|
[node name="matador" parent="Matador/Armature/Skeleton3D" index="0" unique_id=593562242]
|
||||||
transform = Transform3D(0.8642424, 0, 0.50307554, 0, 1, 0, -0.50307554, 0, 0.8642424, 0, 0, 0)
|
transform = Transform3D(0.8642424, 0, 0.50307554, 0, 1, 0, -0.50307554, 0, 0.8642424, 0, 0, 0)
|
||||||
|
|
||||||
[node name="Muleta" parent="Matador/Armature/Skeleton3D" index="1" unique_id=988244930]
|
[node name="Muleta" parent="Matador/Armature/Skeleton3D" index="1" unique_id=978680467]
|
||||||
cast_shadow = 0
|
cast_shadow = 0
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=1531255459]
|
[node name="Camera3D" type="Camera3D" parent="." unique_id=1531255459]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://46y10alai56q" path="res://main_menu.gd" id="1_menu"]
|
[ext_resource type="Script" uid="uid://46y10alai56q" path="res://main_menu.gd" id="1_menu"]
|
||||||
[ext_resource type="VideoStream" uid="uid://csxxlnq74fej4" path="res://Assets/intro_bg.ogv" id="2_video"]
|
[ext_resource type="VideoStream" uid="uid://csxxlnq74fej4" path="res://Assets/intro_bg.ogv" id="2_video"]
|
||||||
[ext_resource type="Shader" path="res://menu_blur.gdshader" id="3_blur"]
|
[ext_resource type="Shader" uid="uid://bmdtg6qlh7wri" path="res://menu_blur.gdshader" id="3_blur"]
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="mat_blur"]
|
[sub_resource type="ShaderMaterial" id="mat_blur"]
|
||||||
shader = ExtResource("3_blur")
|
shader = ExtResource("3_blur")
|
||||||
@@ -74,7 +74,6 @@ grow_horizontal = 2
|
|||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
ratio = 1.7778
|
ratio = 1.7778
|
||||||
stretch_mode = 2
|
|
||||||
|
|
||||||
[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="VideoAspect" unique_id=374854265]
|
[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="VideoAspect" unique_id=374854265]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@@ -281,7 +280,7 @@ bbcode_enabled = true
|
|||||||
text = "[center][font_size=32][color=#d9a833]Bullosseum[/color][/font_size]
|
text = "[center][font_size=32][color=#d9a833]Bullosseum[/color][/font_size]
|
||||||
|
|
||||||
[b][color=#d9a833]Siim Raud[/color][/b] [url=https://instagram.com/siimpressionist]@siimpressionist[/url]
|
[b][color=#d9a833]Siim Raud[/color][/b] [url=https://instagram.com/siimpressionist]@siimpressionist[/url]
|
||||||
[font_size=20]Artist[/font_size]
|
[font_size=20]Artist / Animator[/font_size]
|
||||||
|
|
||||||
[b][color=#d9a833]Richard Aasa[/color][/b] [url=https://instagram.com/aasarichard]@aasarichard[/url]
|
[b][color=#d9a833]Richard Aasa[/color][/b] [url=https://instagram.com/aasarichard]@aasarichard[/url]
|
||||||
[font_size=20]Programmer[/font_size]
|
[font_size=20]Programmer[/font_size]
|
||||||
|
|||||||
@@ -4,30 +4,17 @@
|
|||||||
[ext_resource type="Script" uid="uid://cpyq2r5cq653g" path="res://camera_iso.gd" id="2_camera_iso"]
|
[ext_resource type="Script" uid="uid://cpyq2r5cq653g" path="res://camera_iso.gd" id="2_camera_iso"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bun14xmk20pcj" path="res://Assets/bull.fbx" id="4_e80uo"]
|
[ext_resource type="PackedScene" uid="uid://bun14xmk20pcj" path="res://Assets/bull.fbx" id="4_e80uo"]
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_chest"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_kne1u"]
|
||||||
radius = 0.35
|
radius = 0.7495117
|
||||||
|
height = 1.845398
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_belly"]
|
|
||||||
radius = 0.4
|
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_hips"]
|
|
||||||
radius = 0.3
|
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody3D" unique_id=165510044]
|
[node name="Player" type="CharacterBody3D" unique_id=165510044]
|
||||||
collision_layer = 3
|
collision_layer = 3
|
||||||
script = ExtResource("1_xhfnw")
|
script = ExtResource("1_xhfnw")
|
||||||
|
|
||||||
[node name="CollisionChest" type="CollisionShape3D" parent="." unique_id=447032296]
|
[node name="CollisionBull" type="CollisionShape3D" parent="." unique_id=571780801]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.505, -0.2)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.29183745, 0.3197594)
|
||||||
shape = SubResource("SphereShape3D_chest")
|
shape = SubResource("CapsuleShape3D_kne1u")
|
||||||
|
|
||||||
[node name="CollisionBelly" type="CollisionShape3D" parent="." unique_id=571780801]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.505, 0.1)
|
|
||||||
shape = SubResource("SphereShape3D_belly")
|
|
||||||
|
|
||||||
[node name="CollisionHips" type="CollisionShape3D" parent="." unique_id=717874116]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.505, 0.4)
|
|
||||||
shape = SubResource("SphereShape3D_hips")
|
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=1120161087]
|
[node name="Camera3D" type="Camera3D" parent="." unique_id=1120161087]
|
||||||
script = ExtResource("2_camera_iso")
|
script = ExtResource("2_camera_iso")
|
||||||
@@ -35,4 +22,30 @@ script = ExtResource("2_camera_iso")
|
|||||||
[node name="bull" parent="." unique_id=1386495166 instance=ExtResource("4_e80uo")]
|
[node name="bull" parent="." unique_id=1386495166 instance=ExtResource("4_e80uo")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.655, 0.23)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.655, 0.23)
|
||||||
|
|
||||||
|
[node name="Skeleton3D" parent="bull/Armature" parent_id_path=PackedInt32Array(1386495166, 1942410469) index="0" unique_id=550138996]
|
||||||
|
bones/0/position = Vector3(0, 0, 0.009963839)
|
||||||
|
bones/0/rotation = Quaternion(0.7071068, 0, 0, 0.7071067)
|
||||||
|
bones/0/scale = Vector3(1, 1, 1)
|
||||||
|
bones/1/position = Vector3(0.003526625, -0.0043296535, 0.0040827994)
|
||||||
|
bones/1/rotation = Quaternion(-0.00038647873, 0.06909673, 0.9868542, -0.14609687)
|
||||||
|
bones/2/rotation = Quaternion(-0.06929122, 3.2175163e-07, -0.112670615, 0.99121344)
|
||||||
|
bones/4/position = Vector3(0.0045358227, -0.002137695, -0.004238624)
|
||||||
|
bones/4/rotation = Quaternion(-0.24948503, 0.19590352, 0.9416978, -0.11217985)
|
||||||
|
bones/4/scale = Vector3(1.0000001, 0.9999999, 1.0000001)
|
||||||
|
bones/5/rotation = Quaternion(-0.58943665, -0.3660725, -0.22564478, 0.683842)
|
||||||
|
bones/5/scale = Vector3(1, 1.0000001, 0.99999994)
|
||||||
|
bones/6/rotation = Quaternion(0.52910197, 0.15997827, 0.120245, 0.8246207)
|
||||||
|
bones/6/scale = Vector3(1, 1, 1.0000001)
|
||||||
|
bones/17/position = Vector3(0, 0.0009373814, 0.007552617)
|
||||||
|
bones/17/rotation = Quaternion(-9.4156974e-08, 0.7071068, 0.7071067, -9.4156974e-08)
|
||||||
|
bones/19/position = Vector3(-0.003526625, -0.0043296535, 0.0040827994)
|
||||||
|
bones/19/rotation = Quaternion(0.00038647873, 0.06909673, 0.9868542, 0.14609687)
|
||||||
|
bones/20/rotation = Quaternion(-0.06929122, -3.2227587e-07, 0.11267061, 0.99121344)
|
||||||
|
bones/22/position = Vector3(-0.0045358227, -0.002137695, -0.004238624)
|
||||||
|
bones/22/rotation = Quaternion(0.24948503, 0.19590352, 0.9416978, 0.11217985)
|
||||||
|
bones/23/rotation = Quaternion(-0.58943665, 0.3660725, 0.22564478, 0.683842)
|
||||||
|
bones/23/scale = Vector3(1, 1.0000001, 1)
|
||||||
|
bones/24/rotation = Quaternion(0.529102, -0.15997836, -0.120245025, 0.82462066)
|
||||||
|
bones/24/scale = Vector3(1, 1.0000001, 1)
|
||||||
|
|
||||||
[editable path="bull"]
|
[editable path="bull"]
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ script="rider-plugin.gd"
|
|||||||
|
|
||||||
[presets]
|
[presets]
|
||||||
|
|
||||||
active="off"
|
active="on"
|
||||||
presets="presets.json"
|
presets="presets.json"
|
||||||
|
|||||||
@@ -0,0 +1,935 @@
|
|||||||
|
extends CharacterBody3D
|
||||||
|
## Dark-Souls-style boss bear with a learnable moveset. From NEUTRAL it weighs its
|
||||||
|
## options by the range band to the bull and commits to one telegraphed routine —
|
||||||
|
## wind-up (tell) → active (the hit) → recovery (the player's punish window). A routine
|
||||||
|
## always plays to the end (hyper-armour: it takes damage mid-swing but isn't knocked out
|
||||||
|
## of it), so every attack is readable and punishable. Variety comes from which routine
|
||||||
|
## NEUTRAL picks (weighted per range band); predictability from each routine being fixed
|
||||||
|
## once entered. Below bear_enrage_frac HP it enters phase 2: snappier tells/recovery and
|
||||||
|
## longer combo chains.
|
||||||
|
##
|
||||||
|
## The bear only wounds the bull with its swings and slams — walking or leaping into the
|
||||||
|
## bull does nothing. It plugs into the bull's combat interface: it joins the &"matador"
|
||||||
|
## group so every bull ability already targets it (apply_ability_hit), and mauls the bull
|
||||||
|
## via take_sword_hit.
|
||||||
|
|
||||||
|
# Positioning / neutral states, then the three attack routines, then reactions.
|
||||||
|
enum State { NEUTRAL, STALK, PROWL, SWIPE, SMASH, LEAP, STAGGER, DEAD }
|
||||||
|
# Every attack routine runs through these three beats.
|
||||||
|
enum Phase { WINDUP, ACTIVE, RECOVER }
|
||||||
|
|
||||||
|
signal killed
|
||||||
|
signal health_changed(current: int, max_hp: int)
|
||||||
|
|
||||||
|
var _state: State = State.NEUTRAL
|
||||||
|
var _phase: Phase = Phase.WINDUP
|
||||||
|
var _bull: CharacterBody3D = null
|
||||||
|
var _skeleton: Skeleton3D = null
|
||||||
|
var _anim_player: AnimationPlayer = null
|
||||||
|
var _blood_burst: CPUParticles3D = null
|
||||||
|
|
||||||
|
var _hp: int = 10
|
||||||
|
var _enraged: bool = false
|
||||||
|
var _phase_timer: float = 0.0
|
||||||
|
var _active_len: float = 0.8
|
||||||
|
var _think_timer: float = 0.0
|
||||||
|
var _state_timer: float = 0.0
|
||||||
|
var _stagger_timer: float = 0.0
|
||||||
|
var _stagger_cd: float = 0.0 # poise window after a stagger during which hits don't stun
|
||||||
|
var _hits_done: int = 0
|
||||||
|
var _leap_airborne: bool = false
|
||||||
|
# Solved per-leap so the arc lands on the clip's impact frame: the launch vertical speed
|
||||||
|
# and the custom gravity used only while airborne (so peak height and airtime are tuned
|
||||||
|
# independently of world gravity).
|
||||||
|
var _leap_up: float = 0.0
|
||||||
|
var _leap_grav: float = 0.0
|
||||||
|
# Attack routines queued to fire back-to-back as a combo, popped when the current one
|
||||||
|
# finishes its recovery. Element type is State.
|
||||||
|
var _combo_queue: Array = []
|
||||||
|
|
||||||
|
# Resolved at runtime from the rig's clip list — the Bear.glb ships Rigify-named clips
|
||||||
|
# (RUN_ON4LEGS, IDLE_standing, SMASH_HIT, …) that may import with any prefix, so we match
|
||||||
|
# by substring rather than hard-coding the full track names.
|
||||||
|
var _anim_idle: StringName = &"" # IDLE_ON4LEGS — neutral / recovery (the punish pose)
|
||||||
|
var _anim_run: StringName = &"" # RUN_ON4LEGS — prowl approach / neutral coast
|
||||||
|
var _anim_walk2: StringName = &"" # WALK_ON2LEGS — the 2-leg stalk-in
|
||||||
|
var _anim_stand: StringName = &"" # IDLE_standing — reared-up wind-up / roar
|
||||||
|
var _anim_swipe: StringName = &"" # ONE_TWO_HIT — double swipe
|
||||||
|
var _anim_smash: StringName = &"" # SMASH_HIT — overhead smash
|
||||||
|
var _anim_leap: StringName = &"" # JUMP_SMASH — leap slam
|
||||||
|
var _anim_death: StringName = &"" # DEATH — death
|
||||||
|
|
||||||
|
#take hit flashing
|
||||||
|
var _hit_flash_active := false
|
||||||
|
var _hit_flash_tween: Tween
|
||||||
|
|
||||||
|
const _ACCEL_FACTOR: float = 9.0
|
||||||
|
# Hard deceleration for planted states (wind-up / recovery / neutral settle) so the bear
|
||||||
|
# never coasts across the ground with a stationary pose playing (no foot-sliding).
|
||||||
|
const _PLANT_DECEL: float = 22.0
|
||||||
|
# The Bear.glb clips are authored at 30 fps (see Bear.glb.import); leap timing is keyed by
|
||||||
|
# frame, so convert frames→seconds with this.
|
||||||
|
const _ANIM_FPS: float = 30.0
|
||||||
|
# How close the bear closes on the bull before it stops advancing (roughly the bear+bull
|
||||||
|
# body radii). Its swings still reach well past this; it just never tries to occupy the
|
||||||
|
# bull's spot, so it doesn't shove up onto it.
|
||||||
|
const _BULL_STANDOFF: float = 1.9
|
||||||
|
|
||||||
|
@onready var _mesh: Node3D = $bear_model
|
||||||
|
@onready var _hit_area: Area3D = $HitArea
|
||||||
|
var _dust: CPUParticles3D = null
|
||||||
|
# Claw-slash bursts bound to the paw bones, fired one per hit of the 1-2 punch.
|
||||||
|
var _claw_l: CPUParticles3D = null
|
||||||
|
var _claw_r: CPUParticles3D = null
|
||||||
|
|
||||||
|
|
||||||
|
const RigidSkin = preload("res://rigid_skin.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group(&"matador") # so every existing bull ability already targets the bear
|
||||||
|
add_to_group(&"bear")
|
||||||
|
_hp = maxi(1, int(DP.f("bear_max_hp")))
|
||||||
|
health_changed.emit(_hp, _hp)
|
||||||
|
_skeleton = _find_skeleton(_mesh)
|
||||||
|
_anim_player = _find_anim_player(_mesh)
|
||||||
|
# Web (Mali/ANGLE) can't run Compatibility vertex-skinning; rebuild the bear as
|
||||||
|
# non-skinned bone-attached pieces there. Desktop keeps smooth GPU skinning.
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
RigidSkin.convert_tree(self)
|
||||||
|
if _anim_player:
|
||||||
|
_anim_idle = _resolve_anim(["IDLE_ON4LEGS", "IDLE"])
|
||||||
|
_anim_run = _resolve_anim(["RUN_ON4LEGS", "RUN"])
|
||||||
|
_anim_walk2 = _resolve_anim(["WALK_ON2LEGS", "WALK", "IDLE_standing"])
|
||||||
|
_anim_stand = _resolve_anim(["IDLE_standing", "WALK_ON2LEGS"])
|
||||||
|
_anim_swipe = _resolve_anim(["ONE_TWO_HIT", "SMASH_HIT"])
|
||||||
|
_anim_smash = _resolve_anim(["SMASH_HIT", "ONE_TWO_HIT"])
|
||||||
|
_anim_leap = _resolve_anim(["JUMP_SMASH", "SMASH_HIT"])
|
||||||
|
_anim_death = _resolve_anim(["DEATH"])
|
||||||
|
# Loop the locomotion / idle clips; the attack clips (swipe/smash/leap) are one-shot,
|
||||||
|
# so their on-ground settle frames play once and then hold instead of looping.
|
||||||
|
for a in [_anim_idle, _anim_run, _anim_walk2, _anim_stand]:
|
||||||
|
_ensure_loop(a)
|
||||||
|
for a in [_anim_swipe, _anim_smash, _anim_leap, _anim_death]:
|
||||||
|
_set_no_loop(a)
|
||||||
|
_anim_player.playback_default_blend_time = 0.12
|
||||||
|
_hit_area.body_entered.connect(_on_body_entered)
|
||||||
|
_blood_burst = preload("res://blood_burst.gd").new()
|
||||||
|
add_child(_blood_burst)
|
||||||
|
_dust = _make_slam_dust()
|
||||||
|
add_child(_dust)
|
||||||
|
_setup_claws()
|
||||||
|
_enter_neutral()
|
||||||
|
|
||||||
|
#take hit flashing
|
||||||
|
func _hit_flash() -> void:
|
||||||
|
if _mesh == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
if _hit_flash_tween:
|
||||||
|
_hit_flash_tween.kill()
|
||||||
|
|
||||||
|
_hit_flash_active = true
|
||||||
|
|
||||||
|
# Make the bear red
|
||||||
|
for child in _mesh.find_children("*", "MeshInstance3D", true, false):
|
||||||
|
var mesh := child as MeshInstance3D
|
||||||
|
if mesh:
|
||||||
|
mesh.material_overlay = _make_hit_overlay()
|
||||||
|
|
||||||
|
# Flash for 0.1 sec, then remove
|
||||||
|
_hit_flash_tween = create_tween()
|
||||||
|
_hit_flash_tween.tween_interval(0.10)
|
||||||
|
_hit_flash_tween.tween_callback(_clear_hit_flash)
|
||||||
|
|
||||||
|
func _make_hit_overlay() -> StandardMaterial3D:
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = Color(1.0, 0.05, 0.05, 1.0)
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
return mat
|
||||||
|
|
||||||
|
func _clear_hit_flash() -> void:
|
||||||
|
if _mesh == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
for child in _mesh.find_children("*", "MeshInstance3D", true, false):
|
||||||
|
var mesh := child as MeshInstance3D
|
||||||
|
if mesh:
|
||||||
|
mesh.material_overlay = null
|
||||||
|
|
||||||
|
_hit_flash_active = false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Bind a claw-slash emitter to each paw bone (hand_l / hand_r) via a BoneAttachment3D, so
|
||||||
|
# a burst fired on a swipe hit erupts from that paw wherever the animation has it.
|
||||||
|
func _setup_claws() -> void:
|
||||||
|
if _skeleton == null:
|
||||||
|
return
|
||||||
|
_claw_r = _attach_claw("hand_r")
|
||||||
|
_claw_l = _attach_claw("hand_l")
|
||||||
|
|
||||||
|
|
||||||
|
func _attach_claw(bone: String) -> CPUParticles3D:
|
||||||
|
if _skeleton.find_bone(bone) == -1:
|
||||||
|
return null
|
||||||
|
var attach := BoneAttachment3D.new()
|
||||||
|
attach.bone_name = bone
|
||||||
|
_skeleton.add_child(attach)
|
||||||
|
var claw := _make_claw_slash()
|
||||||
|
attach.add_child(claw)
|
||||||
|
return claw
|
||||||
|
|
||||||
|
|
||||||
|
func get_hp() -> int:
|
||||||
|
return _hp
|
||||||
|
|
||||||
|
|
||||||
|
func _acquire_bull() -> void:
|
||||||
|
if _bull != null:
|
||||||
|
return
|
||||||
|
var players := get_tree().get_nodes_in_group(&"player")
|
||||||
|
if not players.is_empty():
|
||||||
|
_bull = players[0] as CharacterBody3D
|
||||||
|
# The bear only ever hits via distance / area checks, never body collision, so let
|
||||||
|
# it pass through the bull. Otherwise the big capsule rides up and gets stuck on top
|
||||||
|
# of the bull when it lunges or lands a leap on it.
|
||||||
|
if _bull != null:
|
||||||
|
add_collision_exception_with(_bull)
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
if not is_on_floor():
|
||||||
|
# The leap uses its own gravity mid-flight so its arc height and airtime can be
|
||||||
|
# tuned to land on the clip's impact frame; everything else falls under world gravity.
|
||||||
|
if _state == State.LEAP and _phase == Phase.ACTIVE and _leap_grav > 0.0:
|
||||||
|
velocity.y -= _leap_grav * delta
|
||||||
|
else:
|
||||||
|
velocity += get_gravity() * delta
|
||||||
|
|
||||||
|
if _state == State.DEAD:
|
||||||
|
velocity.x = 0.0
|
||||||
|
velocity.z = 0.0
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
|
_acquire_bull()
|
||||||
|
_update_enrage()
|
||||||
|
_stagger_cd = maxf(0.0, _stagger_cd - delta)
|
||||||
|
|
||||||
|
match _state:
|
||||||
|
State.NEUTRAL: _tick_neutral(delta)
|
||||||
|
State.STALK: _tick_stalk(delta)
|
||||||
|
State.PROWL: _tick_prowl(delta)
|
||||||
|
State.SWIPE: _tick_swipe(delta)
|
||||||
|
State.SMASH: _tick_smash(delta)
|
||||||
|
State.LEAP: _tick_leap(delta)
|
||||||
|
State.STAGGER: _tick_stagger(delta)
|
||||||
|
|
||||||
|
move_and_slide()
|
||||||
|
|
||||||
|
|
||||||
|
# ── Phase 2 ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _update_enrage() -> void:
|
||||||
|
if _enraged:
|
||||||
|
return
|
||||||
|
if _hp <= int(ceil(DP.f("bear_max_hp") * DP.f("bear_enrage_frac"))):
|
||||||
|
_enraged = true
|
||||||
|
# Roar: a beat of stagger-free wind-up with a screen shake, so the phase flip reads.
|
||||||
|
_blood_burst.burst(global_position + Vector3(0.0, 1.6, 0.0), Vector3.UP)
|
||||||
|
_shake(0.9)
|
||||||
|
|
||||||
|
|
||||||
|
# Timers that shape readability (tells + recovery) shrink in phase 2; active windows and
|
||||||
|
# the clip-locked leap timings are left alone.
|
||||||
|
func _scaled(t: float) -> float:
|
||||||
|
return t * (DP.f("bear_enrage_scale") if _enraged else 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
# ── NEUTRAL: think, then commit ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _enter_neutral() -> void:
|
||||||
|
_state = State.NEUTRAL
|
||||||
|
# A short, lightly-varied beat so the bear flows from one action into the next; phase 2
|
||||||
|
# shrinks it further. Keep the jitter small so it reads as eager, not hesitant.
|
||||||
|
var think := DP.f("bear_think_time")
|
||||||
|
_think_timer = _scaled(think * randf_range(0.7, 1.3))
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_neutral(delta: float) -> void:
|
||||||
|
_decelerate(_PLANT_DECEL, delta)
|
||||||
|
if _bull != null:
|
||||||
|
_face_point(_bull.global_position, delta)
|
||||||
|
# Coast on the run clip while residual speed bleeds off, then settle to idle — never
|
||||||
|
# hold the idle pose while still sliding.
|
||||||
|
_play(_anim_run if _horizontal_speed() > 1.0 else _anim_idle)
|
||||||
|
_think_timer -= delta
|
||||||
|
if _think_timer <= 0.0:
|
||||||
|
_decide()
|
||||||
|
|
||||||
|
|
||||||
|
# Pop the next queued combo step, or pick a fresh routine weighted by the current range
|
||||||
|
# band. Weights lean into pressure (and chaining) in phase 2.
|
||||||
|
func _decide() -> void:
|
||||||
|
if not _combo_queue.is_empty():
|
||||||
|
var next: State = _combo_queue.pop_front()
|
||||||
|
if _range_allows(next):
|
||||||
|
_start_routine(next)
|
||||||
|
return
|
||||||
|
_combo_queue.clear()
|
||||||
|
|
||||||
|
if _bull == null:
|
||||||
|
_start_routine(State.PROWL)
|
||||||
|
return
|
||||||
|
|
||||||
|
var dist := _flat_dist_to_bull()
|
||||||
|
var options: Array
|
||||||
|
if dist <= DP.f("bear_close_range"):
|
||||||
|
# In strike range: punch or overhead (both step in during the swing).
|
||||||
|
options = [[State.SWIPE, 3.0], [State.SMASH, 3.0 if _enraged else 2.5]]
|
||||||
|
elif dist <= DP.f("bear_mid_range"):
|
||||||
|
# Mid: mostly walk the bull down on two legs; occasionally leap the gap.
|
||||||
|
options = [[State.STALK, 3.0], [State.LEAP, 2.0 if _enraged else 1.5]]
|
||||||
|
else:
|
||||||
|
# Far: lope in on all fours, or spring across with a leap.
|
||||||
|
options = [[State.PROWL, 3.0], [State.LEAP, 1.5]]
|
||||||
|
_start_routine(_weighted_pick(options))
|
||||||
|
|
||||||
|
|
||||||
|
# A queued combo step only fires if the bull is still roughly in its band, so a chain
|
||||||
|
# doesn't whiff into empty air after the player rolls away.
|
||||||
|
func _range_allows(routine: State) -> bool:
|
||||||
|
if _bull == null:
|
||||||
|
return routine == State.PROWL
|
||||||
|
var dist := _flat_dist_to_bull()
|
||||||
|
match routine:
|
||||||
|
State.SWIPE, State.SMASH:
|
||||||
|
return dist <= DP.f("bear_close_range") * 1.4
|
||||||
|
State.LEAP:
|
||||||
|
return dist <= DP.f("bear_mid_range") * 1.5
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _start_routine(routine: State) -> void:
|
||||||
|
match routine:
|
||||||
|
State.SWIPE: _start_swipe()
|
||||||
|
State.SMASH: _start_smash()
|
||||||
|
State.LEAP: _start_leap()
|
||||||
|
State.STALK: _start_stalk()
|
||||||
|
State.PROWL: _start_prowl()
|
||||||
|
_: _enter_neutral()
|
||||||
|
|
||||||
|
|
||||||
|
# When a routine's recovery ends: fire the next combo step if one is queued, else think.
|
||||||
|
func _finish_routine() -> void:
|
||||||
|
if not _combo_queue.is_empty():
|
||||||
|
_decide()
|
||||||
|
else:
|
||||||
|
_enter_neutral()
|
||||||
|
|
||||||
|
|
||||||
|
# ── STALK: a menacing 2-leg walk-in that closes the gap into strike range ────────────
|
||||||
|
|
||||||
|
func _start_stalk() -> void:
|
||||||
|
_state = State.STALK
|
||||||
|
_state_timer = DP.f("bear_stalk_time")
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_stalk(delta: float) -> void:
|
||||||
|
_state_timer -= delta
|
||||||
|
if _bull == null or _state_timer <= 0.0 or _flat_dist_to_bull() <= DP.f("bear_close_range"):
|
||||||
|
_enter_neutral()
|
||||||
|
return
|
||||||
|
var to_bull := _flat_to_bull()
|
||||||
|
_accelerate(to_bull.normalized(), DP.f("bear_stalk_speed"), delta)
|
||||||
|
_face_point(_bull.global_position, delta)
|
||||||
|
_play(_anim_walk2)
|
||||||
|
|
||||||
|
|
||||||
|
# ── PROWL: lope in on all fours to close a big gap ─────────────────────────────────
|
||||||
|
|
||||||
|
func _start_prowl() -> void:
|
||||||
|
_state = State.PROWL
|
||||||
|
_state_timer = 1.6
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_prowl(delta: float) -> void:
|
||||||
|
_state_timer -= delta
|
||||||
|
if _bull == null or _state_timer <= 0.0 or _flat_dist_to_bull() <= DP.f("bear_mid_range") * 0.9:
|
||||||
|
_enter_neutral()
|
||||||
|
return
|
||||||
|
var to_bull := _flat_to_bull()
|
||||||
|
_accelerate(to_bull.normalized(), DP.f("bear_run_speed"), delta)
|
||||||
|
_face_point(_bull.global_position, delta)
|
||||||
|
_play(_anim_run)
|
||||||
|
|
||||||
|
|
||||||
|
# ── SWIPE: Double Swipe (ONE_TWO_HIT), two quick hits ──────────────────────────────
|
||||||
|
|
||||||
|
func _start_swipe() -> void:
|
||||||
|
_state = State.SWIPE
|
||||||
|
_phase = Phase.WINDUP
|
||||||
|
_phase_timer = _scaled(DP.f("bear_swipe_windup"))
|
||||||
|
_hits_done = 0
|
||||||
|
_play(_anim_stand)
|
||||||
|
# Sometimes chain the big smash after the swipe (always in phase 2) — the signature
|
||||||
|
# close string the player learns to bait and punish.
|
||||||
|
if _combo_queue.is_empty() and (_enraged or randf() < DP.f("bear_combo_chance")):
|
||||||
|
_combo_queue.append(State.SMASH)
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_swipe(delta: float) -> void:
|
||||||
|
match _phase:
|
||||||
|
Phase.WINDUP:
|
||||||
|
_decelerate(_PLANT_DECEL, delta) # rear up in place — no slide
|
||||||
|
_face_point_if_bull(delta) # track hard before committing
|
||||||
|
_play(_anim_stand)
|
||||||
|
_phase_timer -= delta
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_begin_attack_active(_anim_swipe, 0.8)
|
||||||
|
Phase.ACTIVE:
|
||||||
|
_lunge_and_track(DP.f("bear_lunge_speed"), delta)
|
||||||
|
_phase_timer -= delta
|
||||||
|
var frac := 1.0 - _phase_timer / maxf(_active_len, 0.001)
|
||||||
|
if _hits_done == 0 and frac >= 0.25:
|
||||||
|
_hits_done = 1
|
||||||
|
_try_hit(DP.f("bear_swipe_reach"))
|
||||||
|
_claw_slash(true) # first paw (right) rakes across
|
||||||
|
elif _hits_done == 1 and frac >= 0.65:
|
||||||
|
_hits_done = 2
|
||||||
|
_try_hit(DP.f("bear_swipe_reach"))
|
||||||
|
_claw_slash(false) # back-hand with the left paw
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_begin_recover(DP.f("bear_swipe_recover"))
|
||||||
|
Phase.RECOVER:
|
||||||
|
_tick_recover(delta)
|
||||||
|
|
||||||
|
|
||||||
|
# ── SMASH: Overhead Smash (SMASH_HIT), slower tell, big punish window ────────────────
|
||||||
|
|
||||||
|
func _start_smash() -> void:
|
||||||
|
_state = State.SMASH
|
||||||
|
_phase = Phase.WINDUP
|
||||||
|
_phase_timer = _scaled(DP.f("bear_smash_windup"))
|
||||||
|
_hits_done = 0
|
||||||
|
_play(_anim_stand)
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_smash(delta: float) -> void:
|
||||||
|
match _phase:
|
||||||
|
Phase.WINDUP:
|
||||||
|
_decelerate(_PLANT_DECEL, delta)
|
||||||
|
_face_point_if_bull(delta)
|
||||||
|
_play(_anim_stand)
|
||||||
|
_phase_timer -= delta
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_begin_attack_active(_anim_smash, 0.9)
|
||||||
|
Phase.ACTIVE:
|
||||||
|
# The overhead steps in less than the swipe — it's a commitment, not a rush.
|
||||||
|
_lunge_and_track(DP.f("bear_lunge_speed") * 0.5, delta)
|
||||||
|
_phase_timer -= delta
|
||||||
|
var frac := 1.0 - _phase_timer / maxf(_active_len, 0.001)
|
||||||
|
if _hits_done == 0 and frac >= 0.4:
|
||||||
|
_hits_done = 1
|
||||||
|
_slam(DP.f("bear_smash_radius"), 0.8)
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_begin_recover(DP.f("bear_smash_recover"))
|
||||||
|
Phase.RECOVER:
|
||||||
|
_tick_recover(delta)
|
||||||
|
|
||||||
|
|
||||||
|
# Shared wind-up → active handoff for the melee swings: start the clip (sped up for snappy
|
||||||
|
# punches) and size the active window to the sped-up clip length.
|
||||||
|
func _begin_attack_active(clip: StringName, fallback_len: float) -> void:
|
||||||
|
_phase = Phase.ACTIVE
|
||||||
|
var spd := maxf(DP.f("bear_attack_anim_speed"), 0.1)
|
||||||
|
_active_len = _anim_length(clip, fallback_len) / spd
|
||||||
|
_phase_timer = _active_len
|
||||||
|
_play_attack(clip, spd)
|
||||||
|
|
||||||
|
|
||||||
|
# ── LEAP: Leap Slam (JUMP_SMASH) — clip-synced ballistic gap-closer + AoE ────────────
|
||||||
|
# The one JUMP_SMASH clip runs start-to-finish across the whole routine: it plays the
|
||||||
|
# crouch on the ground (wind-up), springs at the takeoff frame, the arc is solved so the
|
||||||
|
# paws hit ground exactly on the impact frame, and the last frames settle on the ground.
|
||||||
|
|
||||||
|
func _start_leap() -> void:
|
||||||
|
_state = State.LEAP
|
||||||
|
_phase = Phase.WINDUP
|
||||||
|
_hits_done = 0
|
||||||
|
_leap_airborne = false
|
||||||
|
_leap_grav = 0.0
|
||||||
|
_active_len = _anim_length(_anim_leap, 1.0)
|
||||||
|
_play_attack(_anim_leap, 1.0) # play from frame 0 at real speed, grounded crouch
|
||||||
|
_phase_timer = DP.f("bear_leap_takeoff_frame") / _ANIM_FPS # ground time before the spring
|
||||||
|
# In phase 2 a leap sometimes follows straight into a swipe once it lands close.
|
||||||
|
if _enraged and _combo_queue.is_empty() and randf() < 0.5:
|
||||||
|
_combo_queue.append(State.SWIPE)
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_leap(delta: float) -> void:
|
||||||
|
match _phase:
|
||||||
|
Phase.WINDUP:
|
||||||
|
_decelerate(_PLANT_DECEL, delta) # crouch in place
|
||||||
|
_face_point_if_bull(delta) # aim the pounce, tracking the bull
|
||||||
|
_phase_timer -= delta
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_launch_leap()
|
||||||
|
Phase.ACTIVE:
|
||||||
|
_phase_timer -= delta
|
||||||
|
if not is_on_floor():
|
||||||
|
_leap_airborne = true
|
||||||
|
elif _leap_airborne or _phase_timer <= 0.0:
|
||||||
|
velocity.x = 0.0
|
||||||
|
velocity.z = 0.0
|
||||||
|
_slam(DP.f("bear_slam_radius"), 0.7)
|
||||||
|
_phase = Phase.RECOVER
|
||||||
|
_phase_timer = _scaled(DP.f("bear_leap_recover"))
|
||||||
|
Phase.RECOVER:
|
||||||
|
_decelerate(_PLANT_DECEL, delta)
|
||||||
|
# Let the clip's on-ground settle frames finish first, then idle out the recovery.
|
||||||
|
if _anim_player != null and _anim_player.is_playing() \
|
||||||
|
and _anim_player.current_animation == _anim_leap:
|
||||||
|
return
|
||||||
|
_play(_anim_idle)
|
||||||
|
_phase_timer -= delta
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_finish_routine()
|
||||||
|
|
||||||
|
|
||||||
|
func _launch_leap() -> void:
|
||||||
|
_phase = Phase.ACTIVE
|
||||||
|
_leap_airborne = false
|
||||||
|
var target := _bull.global_position if _bull != null else global_position
|
||||||
|
var flat := target - global_position
|
||||||
|
flat.y = 0.0
|
||||||
|
var dist := flat.length()
|
||||||
|
var dir := flat.normalized() if dist > 0.1 else _facing()
|
||||||
|
_face_dir(dir, 1.0)
|
||||||
|
# Airtime = the clip span between the takeoff and impact frames, so the paws hit ground
|
||||||
|
# on the impact frame (25). A custom leap gravity lets the arc reach bear_leap_height in
|
||||||
|
# exactly that airtime regardless of world gravity: for a symmetric hop of airtime T to
|
||||||
|
# peak height H, v_up = 4H/T and g = 8H/T².
|
||||||
|
var air_t := maxf(
|
||||||
|
(DP.f("bear_leap_impact_frame") - DP.f("bear_leap_takeoff_frame")) / _ANIM_FPS, 0.05)
|
||||||
|
var h := maxf(DP.f("bear_leap_height"), 0.1)
|
||||||
|
_leap_up = 4.0 * h / air_t
|
||||||
|
_leap_grav = 8.0 * h / (air_t * air_t)
|
||||||
|
# Land just in front of the bull, not on top of it — the slam radius still catches it.
|
||||||
|
var land_dist := maxf(dist - _BULL_STANDOFF, 0.0)
|
||||||
|
var h_speed := clampf(land_dist / air_t, 0.0, DP.f("bear_leap_speed"))
|
||||||
|
velocity = dir * h_speed + Vector3.UP * _leap_up
|
||||||
|
_phase_timer = air_t + 1.0 # safety fallback if a landing is missed
|
||||||
|
|
||||||
|
|
||||||
|
func _begin_recover(secs: float) -> void:
|
||||||
|
_phase = Phase.RECOVER
|
||||||
|
_phase_timer = _scaled(secs)
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_recover(delta: float) -> void:
|
||||||
|
_decelerate(_PLANT_DECEL, delta)
|
||||||
|
_play(_anim_idle)
|
||||||
|
_phase_timer -= delta
|
||||||
|
if _phase_timer <= 0.0:
|
||||||
|
_finish_routine()
|
||||||
|
|
||||||
|
|
||||||
|
func _tick_stagger(delta: float) -> void:
|
||||||
|
_decelerate(12.0, delta)
|
||||||
|
_stagger_timer -= delta
|
||||||
|
_play(_anim_idle)
|
||||||
|
if _stagger_timer <= 0.0:
|
||||||
|
_stagger_cd = DP.f("bear_stagger_cd") # poise window so it can act before the next stun
|
||||||
|
_enter_neutral()
|
||||||
|
|
||||||
|
|
||||||
|
# Step toward the bull during a swing (so a strike lands even when the bull is a touch out
|
||||||
|
# of reach) while tracking its facing — but plant once close so the bear doesn't shove
|
||||||
|
# through it. Motion here reads as the strike's lunge, under the attack clip.
|
||||||
|
func _lunge_and_track(speed: float, delta: float) -> void:
|
||||||
|
if _bull == null:
|
||||||
|
_decelerate(_PLANT_DECEL, delta)
|
||||||
|
return
|
||||||
|
_face_point(_bull.global_position, delta, DP.f("bear_track_active"))
|
||||||
|
var to_bull := _flat_to_bull()
|
||||||
|
var dist := to_bull.length()
|
||||||
|
if speed > 0.0 and dist > _BULL_STANDOFF:
|
||||||
|
_accelerate(to_bull / dist, speed, delta)
|
||||||
|
else:
|
||||||
|
_decelerate(_PLANT_DECEL, delta)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Hitting the bull ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Directional strike: the bull is mauled inside `reach` of the bear that's facing it
|
||||||
|
# (a fast bull tunnels a thin collider between frames, so distance + facing is the
|
||||||
|
# reliable hit). Returns whether it connected.
|
||||||
|
func _try_hit(reach: float) -> bool:
|
||||||
|
if _bull == null:
|
||||||
|
return false
|
||||||
|
var to_bull := _flat_to_bull()
|
||||||
|
var dist := to_bull.length()
|
||||||
|
if dist > reach or dist < 0.01:
|
||||||
|
return false
|
||||||
|
if _facing().dot(to_bull / dist) < cos(deg_to_rad(DP.f("bear_hit_arc"))):
|
||||||
|
return false
|
||||||
|
_bull.call(&"take_sword_hit", "mauled")
|
||||||
|
_shake(0.55)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# Radial slam (smash / leap landing): hits any way the bull is, within `radius`, with a
|
||||||
|
# ground-pound shake and a burst of dust kicked up at the paws. No facing arc — you're
|
||||||
|
# caught if you're near where it comes down.
|
||||||
|
func _slam(radius: float, shake: float) -> void:
|
||||||
|
_slam_dust()
|
||||||
|
if _bull == null:
|
||||||
|
_shake(shake)
|
||||||
|
return
|
||||||
|
if _flat_dist_to_bull() <= radius:
|
||||||
|
_bull.call(&"take_sword_hit", "mauled")
|
||||||
|
_shake(shake)
|
||||||
|
|
||||||
|
|
||||||
|
# Kick up a one-shot ring of dust at the bear's feet where the slam lands.
|
||||||
|
func _slam_dust() -> void:
|
||||||
|
if _dust == null:
|
||||||
|
return
|
||||||
|
_dust.global_position = global_position + Vector3(0.0, 0.15, 0.0)
|
||||||
|
_dust.restart()
|
||||||
|
_dust.emitting = true
|
||||||
|
|
||||||
|
|
||||||
|
# Tan ground-impact dust: a fast, wide, low burst that arcs up and settles — built in code
|
||||||
|
# so the Bear scene needs no extra particle node.
|
||||||
|
func _make_slam_dust() -> CPUParticles3D:
|
||||||
|
var p := CPUParticles3D.new()
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
mat.vertex_color_use_as_albedo = true
|
||||||
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
|
||||||
|
var sphere := SphereMesh.new()
|
||||||
|
sphere.radius = 0.16
|
||||||
|
sphere.height = 0.32
|
||||||
|
sphere.radial_segments = 4
|
||||||
|
sphere.rings = 2
|
||||||
|
sphere.material = mat
|
||||||
|
|
||||||
|
var ramp := Gradient.new()
|
||||||
|
ramp.set_color(0, Color(0.72, 0.60, 0.40, 0.9))
|
||||||
|
ramp.set_color(1, Color(0.55, 0.45, 0.30, 0.0))
|
||||||
|
|
||||||
|
var scale_curve := Curve.new()
|
||||||
|
scale_curve.add_point(Vector2(0.0, 0.4))
|
||||||
|
scale_curve.add_point(Vector2(0.35, 1.0))
|
||||||
|
scale_curve.add_point(Vector2(1.0, 0.0))
|
||||||
|
|
||||||
|
p.mesh = sphere
|
||||||
|
p.color_ramp = ramp
|
||||||
|
p.scale_amount_curve = scale_curve
|
||||||
|
p.emitting = false
|
||||||
|
p.one_shot = true
|
||||||
|
p.explosiveness = 1.0
|
||||||
|
p.amount = 36
|
||||||
|
p.lifetime = 0.9
|
||||||
|
p.randomness = 0.5
|
||||||
|
p.local_coords = false
|
||||||
|
# A flat-ish disc kicked outward and up from the ground.
|
||||||
|
p.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE
|
||||||
|
p.emission_sphere_radius = 0.5
|
||||||
|
p.direction = Vector3(0.0, 1.0, 0.0)
|
||||||
|
p.spread = 75.0
|
||||||
|
p.gravity = Vector3(0.0, -6.0, 0.0)
|
||||||
|
p.initial_velocity_min = 2.5
|
||||||
|
p.initial_velocity_max = 6.5
|
||||||
|
p.scale_amount_min = 0.6
|
||||||
|
p.scale_amount_max = 1.4
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
# Fire the claw-slash burst on one paw for a swipe hit (right paw first, then left).
|
||||||
|
func _claw_slash(right: bool) -> void:
|
||||||
|
var claw := _claw_r if right else _claw_l
|
||||||
|
if claw == null:
|
||||||
|
return
|
||||||
|
claw.restart()
|
||||||
|
claw.emitting = true
|
||||||
|
|
||||||
|
|
||||||
|
# A short fan of pale streaks raked from a paw — thin boxes aligned to their velocity so
|
||||||
|
# they read as claw marks slashing outward. Built in code and parented to a hand bone.
|
||||||
|
func _make_claw_slash() -> CPUParticles3D:
|
||||||
|
var p := CPUParticles3D.new()
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
mat.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
|
||||||
|
mat.vertex_color_use_as_albedo = true
|
||||||
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
|
||||||
|
# A thin, long box becomes a streak once aligned to its velocity.
|
||||||
|
var streak := BoxMesh.new()
|
||||||
|
streak.size = Vector3(0.03, 0.55, 0.03)
|
||||||
|
streak.material = mat
|
||||||
|
|
||||||
|
var ramp := Gradient.new()
|
||||||
|
ramp.set_color(0, Color(0.85, 0.95, 1.0, 0.95))
|
||||||
|
ramp.set_color(1, Color(0.5, 0.75, 1.0, 0.0))
|
||||||
|
|
||||||
|
var scale_curve := Curve.new()
|
||||||
|
scale_curve.add_point(Vector2(0.0, 1.0))
|
||||||
|
scale_curve.add_point(Vector2(0.7, 1.0))
|
||||||
|
scale_curve.add_point(Vector2(1.0, 0.0))
|
||||||
|
|
||||||
|
p.mesh = streak
|
||||||
|
p.color_ramp = ramp
|
||||||
|
p.scale_amount_curve = scale_curve
|
||||||
|
p.emitting = false
|
||||||
|
p.one_shot = true
|
||||||
|
p.explosiveness = 1.0 # all at once = a single rake, not a stream
|
||||||
|
p.amount = 5 # a handful of parallel claw marks
|
||||||
|
p.lifetime = 0.22
|
||||||
|
p.randomness = 0.2
|
||||||
|
p.local_coords = false # streaks stay in the air where they were raked, then fade
|
||||||
|
p.set_particle_flag(CPUParticles3D.PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, true)
|
||||||
|
# Rake across the paw's swing plane: a tight fan of fast streaks.
|
||||||
|
p.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE
|
||||||
|
p.emission_sphere_radius = 0.12
|
||||||
|
p.direction = Vector3(0.0, 0.0, 1.0)
|
||||||
|
p.spread = 22.0
|
||||||
|
p.gravity = Vector3.ZERO
|
||||||
|
p.initial_velocity_min = 6.0
|
||||||
|
p.initial_velocity_max = 9.0
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
func _shake(amount: float) -> void:
|
||||||
|
if _bull == null:
|
||||||
|
return
|
||||||
|
var cam := _bull.get_node_or_null("Camera3D")
|
||||||
|
if cam:
|
||||||
|
cam.call(&"trigger_hit", clampf(amount, 0.2, 1.0))
|
||||||
|
|
||||||
|
|
||||||
|
# ── Taking damage ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Called by every bull ability (kick / slam / roll) that iterates the matador group.
|
||||||
|
func apply_ability_hit(hit_dir: Vector3, strength: float, _up_boost: float = 0.0) -> void:
|
||||||
|
_acquire_bull()
|
||||||
|
_take_hit(hit_dir, strength)
|
||||||
|
|
||||||
|
|
||||||
|
# Ramming: only a real charge from the bull wounds the bear; a gentle nudge is shrugged
|
||||||
|
# off. The bear never damages the bull by colliding — only its swings/slams do.
|
||||||
|
func _on_body_entered(body: Node3D) -> void:
|
||||||
|
if _state == State.DEAD or not body.is_in_group(&"player"):
|
||||||
|
return
|
||||||
|
var player := body as CharacterBody3D
|
||||||
|
if player.velocity.length() < DP.f("bear_hit_threshold"):
|
||||||
|
return
|
||||||
|
var flat := Vector3(player.velocity.x, 0.0, player.velocity.z)
|
||||||
|
var dir := flat.normalized() if flat.length() > 0.5 else \
|
||||||
|
(global_position - player.global_position).normalized()
|
||||||
|
_take_hit(dir, player.velocity.length())
|
||||||
|
|
||||||
|
|
||||||
|
func _take_hit(hit_dir: Vector3, strength: float) -> void:
|
||||||
|
if _state == State.DEAD:
|
||||||
|
return
|
||||||
|
_hp -= 1
|
||||||
|
_hit_flash()
|
||||||
|
health_changed.emit(_hp, int(DP.f("bear_max_hp")))
|
||||||
|
hit_dir.y = 0.0
|
||||||
|
hit_dir = hit_dir.normalized()
|
||||||
|
_blood_burst.burst(global_position + Vector3(0.0, 1.4, 0.0), hit_dir)
|
||||||
|
_shake(clampf(strength / 15.0, 0.4, 1.0))
|
||||||
|
if _hp <= 0:
|
||||||
|
_die(hit_dir)
|
||||||
|
return
|
||||||
|
# Hyper-armour: a committed swing takes the wound but isn't knocked out of it — only
|
||||||
|
# the vulnerable states (neutral / positioning / a routine's recovery) stagger. This
|
||||||
|
# is what makes the recovery the real punish window.
|
||||||
|
if not _can_stagger():
|
||||||
|
return
|
||||||
|
velocity.x = hit_dir.x * 4.0
|
||||||
|
velocity.z = hit_dir.z * 4.0
|
||||||
|
_combo_queue.clear()
|
||||||
|
_state = State.STAGGER
|
||||||
|
_stagger_timer = DP.f("bear_stagger_time")
|
||||||
|
|
||||||
|
|
||||||
|
func _can_stagger() -> bool:
|
||||||
|
if _stagger_cd > 0.0:
|
||||||
|
return false # poise: recently staggered, so it rides out hits instead of stun-locking
|
||||||
|
match _state:
|
||||||
|
State.NEUTRAL, State.STALK, State.PROWL, State.STAGGER:
|
||||||
|
return true
|
||||||
|
State.SWIPE, State.SMASH, State.LEAP:
|
||||||
|
return _phase == Phase.RECOVER # airborne / mid-swing can't be staggered
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _die(hit_dir: Vector3) -> void:
|
||||||
|
_state = State.DEAD
|
||||||
|
killed.emit()
|
||||||
|
collision_layer = 0
|
||||||
|
if _hit_area:
|
||||||
|
_hit_area.monitoring = false
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
_blood_burst.burst(global_position + Vector3(0.0, 1.4, 0.0), hit_dir)
|
||||||
|
_play(_anim_death)
|
||||||
|
# No death clip on the rig — topple the beast onto its side (about the hit direction,
|
||||||
|
# so it falls the way it was struck) and sink it away, then free.
|
||||||
|
#var fall_axis := Vector3(-hit_dir.z, 0.0, hit_dir.x)
|
||||||
|
#if fall_axis.length() < 0.01:
|
||||||
|
# fall_axis = Vector3.RIGHT
|
||||||
|
#var tween := create_tween()
|
||||||
|
#tween.tween_property(_mesh, "rotation",
|
||||||
|
# _mesh.rotation + fall_axis.normalized() * deg_to_rad(88.0), 0.7)\
|
||||||
|
# .set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||||||
|
#tween.tween_interval(2.5)
|
||||||
|
#tween.tween_property(_mesh, "position:y", _mesh.position.y - 3.0, 1.0)
|
||||||
|
#tween.tween_callback(queue_free)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Interface shared with the matador (guarded elsewhere via has_method) ─────────────
|
||||||
|
|
||||||
|
func is_active() -> bool:
|
||||||
|
return _state != State.DEAD
|
||||||
|
|
||||||
|
|
||||||
|
func ai_state_name() -> String:
|
||||||
|
var n := State.keys()[_state] as String
|
||||||
|
if _state in [State.SWIPE, State.SMASH, State.LEAP]:
|
||||||
|
n += ":" + (Phase.keys()[_phase] as String)
|
||||||
|
if _enraged:
|
||||||
|
n += "*"
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
# ── Movement / geometry helpers ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _flat_to_bull() -> Vector3:
|
||||||
|
var to := _bull.global_position - global_position
|
||||||
|
to.y = 0.0
|
||||||
|
return to
|
||||||
|
|
||||||
|
|
||||||
|
func _flat_dist_to_bull() -> float:
|
||||||
|
return _flat_to_bull().length()
|
||||||
|
|
||||||
|
|
||||||
|
func _horizontal_speed() -> float:
|
||||||
|
return Vector2(velocity.x, velocity.z).length()
|
||||||
|
|
||||||
|
|
||||||
|
func _facing() -> Vector3:
|
||||||
|
return Vector3(sin(_mesh.rotation.y), 0.0, cos(_mesh.rotation.y))
|
||||||
|
|
||||||
|
|
||||||
|
func _accelerate(dir: Vector3, spd: float, delta: float) -> void:
|
||||||
|
velocity.x = move_toward(velocity.x, dir.x * spd, spd * _ACCEL_FACTOR * delta)
|
||||||
|
velocity.z = move_toward(velocity.z, dir.z * spd, spd * _ACCEL_FACTOR * delta)
|
||||||
|
|
||||||
|
|
||||||
|
func _decelerate(rate: float, delta: float) -> void:
|
||||||
|
velocity.x = move_toward(velocity.x, 0.0, rate * delta)
|
||||||
|
velocity.z = move_toward(velocity.z, 0.0, rate * delta)
|
||||||
|
|
||||||
|
|
||||||
|
func _face_point_if_bull(delta: float, rate: float = -1.0) -> void:
|
||||||
|
if _bull != null:
|
||||||
|
_face_point(_bull.global_position, delta, rate)
|
||||||
|
|
||||||
|
|
||||||
|
func _face_point(point: Vector3, delta: float, rate: float = -1.0) -> void:
|
||||||
|
_face_dir(point - global_position, delta, rate)
|
||||||
|
|
||||||
|
|
||||||
|
func _face_dir(dir: Vector3, delta: float, rate: float = -1.0) -> void:
|
||||||
|
if rate < 0.0:
|
||||||
|
rate = DP.f("bear_turn_rate")
|
||||||
|
dir.y = 0.0
|
||||||
|
if dir.length_squared() > 0.01:
|
||||||
|
_mesh.rotation.y = lerp_angle(_mesh.rotation.y, atan2(dir.x, dir.z), delta * rate)
|
||||||
|
|
||||||
|
|
||||||
|
func _weighted_pick(options: Array) -> State:
|
||||||
|
var total := 0.0
|
||||||
|
for o: Array in options:
|
||||||
|
total += o[1]
|
||||||
|
var r := randf() * total
|
||||||
|
for o: Array in options:
|
||||||
|
r -= o[1]
|
||||||
|
if r <= 0.0:
|
||||||
|
return o[0]
|
||||||
|
return options[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
# ── Animation helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _resolve_anim(candidates: Array) -> StringName:
|
||||||
|
if _anim_player == null:
|
||||||
|
return &""
|
||||||
|
var list := _anim_player.get_animation_list()
|
||||||
|
for want: String in candidates:
|
||||||
|
for have: String in list:
|
||||||
|
if have.to_upper().contains(want.to_upper()):
|
||||||
|
return StringName(have)
|
||||||
|
return &""
|
||||||
|
|
||||||
|
|
||||||
|
func _ensure_loop(anim_name: StringName) -> void:
|
||||||
|
if anim_name != &"" and _anim_player.has_animation(anim_name):
|
||||||
|
_anim_player.get_animation(anim_name).loop_mode = Animation.LOOP_LINEAR
|
||||||
|
|
||||||
|
|
||||||
|
func _set_no_loop(anim_name: StringName) -> void:
|
||||||
|
if anim_name != &"" and _anim_player.has_animation(anim_name):
|
||||||
|
_anim_player.get_animation(anim_name).loop_mode = Animation.LOOP_NONE
|
||||||
|
|
||||||
|
|
||||||
|
func _anim_length(anim_name: StringName, fallback: float) -> float:
|
||||||
|
if _anim_player and anim_name != &"" and _anim_player.has_animation(anim_name):
|
||||||
|
return _anim_player.get_animation(anim_name).length
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
|
||||||
|
func _play(anim_name: StringName) -> void:
|
||||||
|
if _anim_player == null or anim_name == &"":
|
||||||
|
return
|
||||||
|
if _anim_player.current_animation == anim_name:
|
||||||
|
return
|
||||||
|
_anim_player.play(anim_name)
|
||||||
|
|
||||||
|
|
||||||
|
# Restart a one-shot attack clip from its first frame at `speed` (even if already current).
|
||||||
|
func _play_attack(anim_name: StringName, speed: float) -> void:
|
||||||
|
if _anim_player == null or anim_name == &"":
|
||||||
|
return
|
||||||
|
_anim_player.play(anim_name, -1.0, speed)
|
||||||
|
_anim_player.seek(0.0, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _find_skeleton(node: Node) -> Skeleton3D:
|
||||||
|
if node is Skeleton3D:
|
||||||
|
return node as Skeleton3D
|
||||||
|
for child: Node in node.get_children():
|
||||||
|
var r := _find_skeleton(child)
|
||||||
|
if r:
|
||||||
|
return r
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _find_anim_player(node: Node) -> AnimationPlayer:
|
||||||
|
if node is AnimationPlayer:
|
||||||
|
return node as AnimationPlayer
|
||||||
|
for child: Node in node.get_children():
|
||||||
|
var r := _find_anim_player(child)
|
||||||
|
if r:
|
||||||
|
return r
|
||||||
|
return null
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cn7fhjkro7637
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
|
||||||
|
func mesh_on():
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
func mesh_off():
|
||||||
|
visible = false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bsbed05ifr18s
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
extends Camera3D
|
|
||||||
|
|
||||||
|
|
||||||
@export var spring_arm: Node3D
|
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
|
||||||
var lerp_power: float = DP.f("cam_lerp_power")
|
|
||||||
position = position.lerp(spring_arm.position, delta * lerp_power)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://b720l6m0ehrj3
|
|
||||||
@@ -42,6 +42,15 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
_user_fov = clamp(_user_fov * 1.1, fov_min, fov_max)
|
_user_fov = clamp(_user_fov * 1.1, fov_min, fov_max)
|
||||||
|
|
||||||
|
|
||||||
|
# Pinch-to-zoom hook for the touch UI. `ratio` is the two-finger spread this frame over
|
||||||
|
# last frame: >1 (fingers parting) narrows the FOV to zoom in, <1 widens it to pull back,
|
||||||
|
# mirroring the scroll-wheel zoom_in/zoom_out feel.
|
||||||
|
func adjust_zoom(ratio: float) -> void:
|
||||||
|
if ratio <= 0.0:
|
||||||
|
return
|
||||||
|
_user_fov = clampf(_user_fov / ratio, fov_min, fov_max)
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
fov = lerpf(fov, _user_fov, delta * 8.0)
|
fov = lerpf(fov, _user_fov, delta * 8.0)
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
|
|
||||||
@onready var spring_arm: SpringArm3D = $SpringArm3D
|
|
||||||
var _desired_length: float = 7.0
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
||||||
_desired_length = DP.f("cam_spring_length")
|
|
||||||
spring_arm.spring_length = _desired_length
|
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
|
||||||
var sensitivity: float = DP.f("cam_sensitivity")
|
|
||||||
var min_vert: float = deg_to_rad(DP.f("cam_min_vert_angle"))
|
|
||||||
var max_vert: float = deg_to_rad(DP.f("cam_max_vert_angle"))
|
|
||||||
var min_zoom: float = DP.f("cam_min_zoom")
|
|
||||||
var max_zoom: float = DP.f("cam_max_zoom")
|
|
||||||
|
|
||||||
if event is InputEventMouseMotion:
|
|
||||||
rotation.y -= event.relative.x * sensitivity
|
|
||||||
rotation.y = wrapf(rotation.y, 0.0, TAU)
|
|
||||||
rotation.x -= event.relative.y * sensitivity
|
|
||||||
rotation.x = clamp(rotation.x, min_vert, max_vert)
|
|
||||||
|
|
||||||
if event.is_action_pressed("zoom_in"):
|
|
||||||
_desired_length = clamp(_desired_length / 1.2, min_zoom, max_zoom)
|
|
||||||
if event.is_action_pressed("zoom_out"):
|
|
||||||
_desired_length = clamp(_desired_length * 1.2, min_zoom, max_zoom)
|
|
||||||
|
|
||||||
if event.is_action_pressed("toggle_mouse_capture"):
|
|
||||||
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
||||||
else:
|
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
spring_arm.spring_length = _desired_length
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://b4td4k8yo5y78
|
|
||||||
@@ -18,6 +18,8 @@ const _COMMANDS := {
|
|||||||
"diff": "list params changed from default",
|
"diff": "list params changed from default",
|
||||||
"toggle": "flip a bool param",
|
"toggle": "flip a bool param",
|
||||||
"list": "print params (optional section)",
|
"list": "print params (optional section)",
|
||||||
|
"level": "select level and reload (level <id> — arena / arena_boss / bear)",
|
||||||
|
"restart": "reload the current level scene",
|
||||||
"clear": "clear the log",
|
"clear": "clear the log",
|
||||||
"help": "show this help",
|
"help": "show this help",
|
||||||
}
|
}
|
||||||
@@ -472,6 +474,12 @@ func _run_command(parts: PackedStringArray) -> bool:
|
|||||||
_cmd_diff()
|
_cmd_diff()
|
||||||
"list":
|
"list":
|
||||||
_cmd_list(parts)
|
_cmd_list(parts)
|
||||||
|
"level":
|
||||||
|
_cmd_level(parts)
|
||||||
|
"restart":
|
||||||
|
_log_ok("Restarting…")
|
||||||
|
get_tree().paused = false
|
||||||
|
get_tree().reload_current_scene()
|
||||||
"clear":
|
"clear":
|
||||||
_log.clear()
|
_log.clear()
|
||||||
"help":
|
"help":
|
||||||
@@ -581,9 +589,9 @@ func _set_param(key: String, meta: Dictionary, raw: String) -> void:
|
|||||||
var lo := meta["min"] as float
|
var lo := meta["min"] as float
|
||||||
var hi := meta["max"] as float
|
var hi := meta["max"] as float
|
||||||
if not raw.is_valid_float() or not is_finite(raw.to_float()):
|
if not raw.is_valid_float() or not is_finite(raw.to_float()):
|
||||||
_log_warn("%s wants a number %s … %s" % [key, _fmt_num(lo), _fmt_num(hi)])
|
_log_warn("%s wants a number" % key)
|
||||||
return
|
return
|
||||||
var v := snappedf(clampf(raw.to_float(), lo, hi), meta["step"] as float)
|
var v := raw.to_float()
|
||||||
DP.set_value(key, v)
|
DP.set_value(key, v)
|
||||||
_log_ok("[b]%s[/b] ← [color=#ffe080]%s[/color]" % [key, _fmt_num(v)])
|
_log_ok("[b]%s[/b] ← [color=#ffe080]%s[/color]" % [key, _fmt_num(v)])
|
||||||
elif meta["type"] == TYPE_BOOL:
|
elif meta["type"] == TYPE_BOOL:
|
||||||
@@ -600,6 +608,25 @@ func _set_param(key: String, meta: Dictionary, raw: String) -> void:
|
|||||||
_log_warn("Cannot set param of type %d" % meta["type"])
|
_log_warn("Cannot set param of type %d" % meta["type"])
|
||||||
|
|
||||||
|
|
||||||
|
func _cmd_level(parts: PackedStringArray) -> void:
|
||||||
|
if parts.size() == 1:
|
||||||
|
var cur := Run.active_level()
|
||||||
|
var cur_name := (cur.id as String) if cur != null else "none"
|
||||||
|
_log_ok("Current level: [b]%s[/b]" % cur_name)
|
||||||
|
var ids: Array[StringName] = Run.level_ids()
|
||||||
|
_log_raw("Available: " + " ".join(ids.map(func(id: StringName) -> String: return str(id))))
|
||||||
|
return
|
||||||
|
var id := StringName(parts[1].to_lower())
|
||||||
|
var level := Run.get_level(id)
|
||||||
|
if level == null:
|
||||||
|
_log_warn("Unknown level '%s'. Try: %s" % [parts[1], " ".join(Run.level_ids().map(func(i: StringName) -> String: return str(i)))])
|
||||||
|
return
|
||||||
|
Run.debug_level_override = level
|
||||||
|
_log_ok("Level set to [b]%s[/b] — reloading…" % str(id))
|
||||||
|
get_tree().paused = false
|
||||||
|
get_tree().change_scene_to_file(level.scene_path)
|
||||||
|
|
||||||
|
|
||||||
func _cmd_list(parts: PackedStringArray) -> void:
|
func _cmd_list(parts: PackedStringArray) -> void:
|
||||||
var filter := parts[1].to_lower() if parts.size() > 1 else ""
|
var filter := parts[1].to_lower() if parts.size() > 1 else ""
|
||||||
var all := DP.get_all()
|
var all := DP.get_all()
|
||||||
|
|||||||
@@ -25,19 +25,96 @@ func _ready() -> void:
|
|||||||
load_saved()
|
load_saved()
|
||||||
|
|
||||||
|
|
||||||
|
# ── Web fullscreen + landscape lock ─────────────────────────────────────────────
|
||||||
|
# Browsers only allow requestFullscreen / screen.orientation.lock from inside a user
|
||||||
|
# gesture, so we piggyback on the first tap/click of the web build to go fullscreen and
|
||||||
|
# lock to landscape. One-shot; input still flows to the menu/game normally (never consumed).
|
||||||
|
var _fs_triggered: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if _fs_triggered or not OS.has_feature("web"):
|
||||||
|
return
|
||||||
|
var gesture := (event is InputEventScreenTouch and (event as InputEventScreenTouch).pressed) \
|
||||||
|
or (event is InputEventMouseButton and (event as InputEventMouseButton).pressed)
|
||||||
|
if gesture:
|
||||||
|
_fs_triggered = true
|
||||||
|
request_fullscreen_landscape()
|
||||||
|
|
||||||
|
|
||||||
|
## Enter browser fullscreen and lock to landscape. Must be called from a user-gesture
|
||||||
|
## context (first tap, or a button press). No-op off web. Android Chrome/Brave honour the
|
||||||
|
## orientation lock; iOS Safari ignores it (unsupported), so the .catch() swallows the reject.
|
||||||
|
func request_fullscreen_landscape() -> void:
|
||||||
|
if not OS.has_feature("web"):
|
||||||
|
return
|
||||||
|
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||||
|
JavaScriptBridge.eval(
|
||||||
|
"(function(){function l(){try{if(screen.orientation&&screen.orientation.lock)"
|
||||||
|
+ "screen.orientation.lock('landscape').catch(function(){});}catch(e){}}"
|
||||||
|
+ "l();document.addEventListener('fullscreenchange',l,{once:true});})()", true)
|
||||||
|
|
||||||
|
|
||||||
## Whether to drive the game with the on-screen touch UI (joystick + buttons)
|
## Whether to drive the game with the on-screen touch UI (joystick + buttons)
|
||||||
## instead of keyboard hints. DisplayServer.is_touchscreen_available() can't be
|
## instead of keyboard hints. Platform detection is cached (it can't change mid-run)
|
||||||
## used here: `emulate_touch_from_mouse` (on for desktop testing) makes it report
|
## so the per-frame/per-input callers don't repay the cost — but the DP force flag is
|
||||||
## true on every desktop. Gate on the actual runtime platform instead — real
|
## re-read every call so the console can toggle the layout live on a desktop build.
|
||||||
## mobile/native and mobile web browsers — plus the DP force flag for testing.
|
var _touch_ui_cached: int = -1 # -1 = not yet detected, 0 = false, 1 = true
|
||||||
|
|
||||||
|
|
||||||
func use_touch_ui() -> bool:
|
func use_touch_ui() -> bool:
|
||||||
if DP.b("force_touch_controls"):
|
if DP.b("force_touch_controls"):
|
||||||
return true
|
return true
|
||||||
return (
|
if _touch_ui_cached == -1:
|
||||||
|
_touch_ui_cached = 1 if _detect_touch_ui() else 0
|
||||||
|
return _touch_ui_cached == 1
|
||||||
|
|
||||||
|
|
||||||
|
## True only on touch-primary devices (phones, tablets). Detection is deliberately
|
||||||
|
## conservative so a desktop never gets the on-screen thumb UI.
|
||||||
|
##
|
||||||
|
## DisplayServer.is_touchscreen_available() is unusable here on two fronts: on native
|
||||||
|
## desktop `emulate_touch_from_mouse` (on for testing) forces it true, and on the web build
|
||||||
|
## it maps to `'ontouchstart' in window`, which desktop Chromium reports true even with no
|
||||||
|
## touchscreen. So on web we ask the browser two questions instead: does it report touch
|
||||||
|
## points, AND is the *primary* pointer coarse? A desktop-with-mouse (even a touch laptop)
|
||||||
|
## answers no to the second and keeps the desktop HUD; a phone/tablet answers yes to both.
|
||||||
|
## maxTouchPoints also catches iPadOS, which masquerades as desktop Safari.
|
||||||
|
func _detect_touch_ui() -> bool:
|
||||||
|
if (
|
||||||
OS.has_feature("mobile")
|
OS.has_feature("mobile")
|
||||||
or OS.has_feature("web_android")
|
or OS.has_feature("web_android")
|
||||||
or OS.has_feature("web_ios")
|
or OS.has_feature("web_ios")
|
||||||
)
|
):
|
||||||
|
return true
|
||||||
|
if not OS.has_feature("web"):
|
||||||
|
return false
|
||||||
|
var js := "(navigator.maxTouchPoints > 0) && window.matchMedia('(pointer: coarse)').matches"
|
||||||
|
var touch_primary: Variant = JavaScriptBridge.eval(js, true)
|
||||||
|
if touch_primary is bool:
|
||||||
|
return touch_primary
|
||||||
|
if touch_primary is int or touch_primary is float:
|
||||||
|
return int(touch_primary) != 0
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
## Whether on-screen debug overlays (touch state + render diagnostics) should show. True on
|
||||||
|
## the DP "touch_debug" flag, or — so it can be switched on from a phone where no console is
|
||||||
|
## reachable — when the page URL carries a `debug` query param (append `?debug=1`).
|
||||||
|
var _url_debug: int = -1
|
||||||
|
|
||||||
|
|
||||||
|
func debug_overlay() -> bool:
|
||||||
|
if DP.b("touch_debug"):
|
||||||
|
return true
|
||||||
|
if _url_debug == -1:
|
||||||
|
_url_debug = 0
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
var has: Variant = JavaScriptBridge.eval(
|
||||||
|
"String(window.location.search).indexOf('debug') >= 0", true)
|
||||||
|
if (has is bool and has) or ((has is int or has is float) and int(has) != 0):
|
||||||
|
_url_debug = 1
|
||||||
|
return _url_debug == 1
|
||||||
|
|
||||||
|
|
||||||
## Returns the first keyboard event bound to an action, or null.
|
## Returns the first keyboard event bound to an action, or null.
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
extends MultiMeshInstance3D
|
extends MultiMeshInstance3D
|
||||||
## A whole stand of cheering spectators rendered as camera-facing billboards in a
|
## A whole stand of cheering spectators rendered as flat billboards in a single draw
|
||||||
## single draw call — the performant replacement for dozens of skinned crowd skeletons.
|
## call — the performant replacement for dozens of skinned crowd skeletons. The sprite
|
||||||
## The sprite sheet is baked from the existing crowd figure by tools/bake_crowd_sheet.gd.
|
## sheet is baked from the existing crowd figure by tools/bake_crowd_sheet.gd.
|
||||||
##
|
##
|
||||||
## Placement mirrors an existing crowd node (default: the sibling PublikNode) so the
|
## Placement comes from CrowdMarker nodes the designer drops into the level (collected
|
||||||
## billboards land exactly where the crowd was authored; if that node is missing it
|
## via the &"crowd_marker" group): each marker gives one spectator its world position and
|
||||||
## falls back to a generated tribune so the scene still populates.
|
## a chosen facing (one of six 60°-spaced headings). If no markers are present the scene
|
||||||
|
## falls back to a generated tribune so it still populates.
|
||||||
|
|
||||||
const SHEET_PATH := "res://Assets/crowd_clap_sheet.png"
|
const SHEET_PATH := "res://Assets/crowd_clap_sheet.png"
|
||||||
const SHADER_PATH := "res://crowd_billboard.gdshader"
|
const SHADER_PATH := "res://crowd_billboard.gdshader"
|
||||||
|
|
||||||
## Node whose descendant figures (names starting with `figure_prefix`) donate their
|
|
||||||
## world positions. Empty = use the generated fallback stand.
|
|
||||||
@export var source_path: NodePath = ^"../PublikNode"
|
|
||||||
@export var figure_prefix: String = "mees"
|
|
||||||
## World height of a spectator billboard, in metres. Sheet aspect sets the width.
|
## World height of a spectator billboard, in metres. Sheet aspect sets the width.
|
||||||
@export var figure_height: float = 2.0
|
@export var figure_height: float = 2.0
|
||||||
@export var frames: int = 8
|
@export var frames: int = 8
|
||||||
@@ -36,7 +33,8 @@ var _rng := RandomNumberGenerator.new()
|
|||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_rng.seed = hash("bullosseum-crowd") # deterministic layout/phases across runs
|
_rng.seed = hash("bullosseum-crowd") # deterministic layout/phases across runs
|
||||||
_build()
|
# Deferred so every CrowdMarker has run _ready and joined the group before we harvest.
|
||||||
|
_build.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
func _build() -> void:
|
func _build() -> void:
|
||||||
@@ -58,71 +56,53 @@ func _build() -> void:
|
|||||||
mat.set_shader_parameter("fps", clap_fps)
|
mat.set_shader_parameter("fps", clap_fps)
|
||||||
material_override = mat
|
material_override = mat
|
||||||
|
|
||||||
var seats := _seat_positions()
|
# Each seat is a (global feet-position, unit forward) pair.
|
||||||
var center := _arena_center(seats)
|
var positions := PackedVector3Array()
|
||||||
|
var facings: Array[Vector3] = []
|
||||||
|
_collect_seats(positions, facings)
|
||||||
|
|
||||||
var mm := MultiMesh.new()
|
var mm := MultiMesh.new()
|
||||||
mm.transform_format = MultiMesh.TRANSFORM_3D
|
mm.transform_format = MultiMesh.TRANSFORM_3D
|
||||||
mm.use_custom_data = true
|
mm.use_custom_data = true
|
||||||
mm.mesh = quad
|
mm.mesh = quad
|
||||||
mm.instance_count = seats.size()
|
mm.instance_count = positions.size()
|
||||||
|
|
||||||
for i in seats.size():
|
for i in positions.size():
|
||||||
var s := 1.0 + _rng.randf_range(-0.06, 0.06) # slight height variety
|
var s := 1.0 + _rng.randf_range(-0.06, 0.06) # slight height variety
|
||||||
mm.set_instance_transform(i, Transform3D(_facing_basis(seats[i], center, s), seats[i]))
|
mm.set_instance_transform(i, Transform3D(_upright_basis(facings[i], s), positions[i]))
|
||||||
var b := 1.0 + _rng.randf_range(-tint_variation, tint_variation)
|
var b := 1.0 + _rng.randf_range(-tint_variation, tint_variation)
|
||||||
mm.set_instance_custom_data(i, Color(_rng.randf(), b, b * 0.99, b * 0.98))
|
mm.set_instance_custom_data(i, Color(_rng.randf(), b, b * 0.99, b * 0.98))
|
||||||
multimesh = mm
|
multimesh = mm
|
||||||
|
|
||||||
|
|
||||||
# Orientation for a seat: the sprite's forward (+Z, the quad's face) points horizontally
|
# Upright basis whose forward (+Z, the quad's face) points along `look`, uniformly
|
||||||
# inward at the arena centre, so each ring segment faces the action instead of the
|
# scaled by `s`. World +Y stays up so spectators never tilt.
|
||||||
# camera. Upright (world +Y), uniformly scaled by `s`.
|
func _upright_basis(look: Vector3, s: float) -> Basis:
|
||||||
func _facing_basis(seat: Vector3, center: Vector3, s: float) -> Basis:
|
|
||||||
var look := center - seat
|
|
||||||
look.y = 0.0
|
look.y = 0.0
|
||||||
look = look.normalized() if look.length() > 0.001 else Vector3(0.0, 0.0, -1.0)
|
look = look.normalized() if look.length() > 0.001 else Vector3(0.0, 0.0, 1.0)
|
||||||
var up := Vector3.UP
|
var up := Vector3.UP
|
||||||
var right := up.cross(look).normalized()
|
|
||||||
var basis := Basis()
|
var basis := Basis()
|
||||||
basis.x = right
|
basis.x = up.cross(look).normalized()
|
||||||
basis.y = up
|
basis.y = up
|
||||||
basis.z = look
|
basis.z = look
|
||||||
return basis.scaled(Vector3.ONE * s)
|
return basis.scaled(Vector3.ONE * s)
|
||||||
|
|
||||||
|
|
||||||
# Horizontal centre the crowd faces — the mean of the seat positions (the ring/stand
|
# Seats from designer-placed CrowdMarker nodes: each contributes its world position and
|
||||||
# centroid), which for an arena crowd is the middle of the pitch.
|
# its chosen 60°-spaced facing. With no markers present, fall back to a generated ring
|
||||||
func _arena_center(seats: PackedVector3Array) -> Vector3:
|
# that faces inward so the scene still shows a crowd.
|
||||||
if seats.is_empty():
|
func _collect_seats(positions: PackedVector3Array, facings: Array[Vector3]) -> void:
|
||||||
return global_position
|
for node in get_tree().get_nodes_in_group(&"crowd_marker"):
|
||||||
var sum := Vector3.ZERO
|
var marker := node as Node3D
|
||||||
for p in seats:
|
if marker == null or not marker.has_method(&"facing_dir"):
|
||||||
sum += p
|
continue
|
||||||
return sum / float(seats.size())
|
positions.append(marker.global_position)
|
||||||
|
facings.append(marker.facing_dir())
|
||||||
|
if positions.is_empty():
|
||||||
|
_fallback_stand(positions, facings)
|
||||||
|
|
||||||
|
|
||||||
# Global feet-positions for every spectator: harvested from the source crowd if present,
|
func _fallback_stand(positions: PackedVector3Array, facings: Array[Vector3]) -> void:
|
||||||
# otherwise the generated fallback tribune.
|
|
||||||
func _seat_positions() -> PackedVector3Array:
|
|
||||||
var out := PackedVector3Array()
|
|
||||||
var src := get_node_or_null(source_path)
|
|
||||||
if src != null:
|
|
||||||
_collect_figures(src, out)
|
|
||||||
if out.is_empty():
|
|
||||||
_fallback_stand(out)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
func _collect_figures(node: Node, out: PackedVector3Array) -> void:
|
|
||||||
if node is Node3D and node != self and (node as Node3D).name.begins_with(figure_prefix):
|
|
||||||
out.append((node as Node3D).global_position)
|
|
||||||
return # don't descend into a figure's own rig
|
|
||||||
for c in node.get_children():
|
|
||||||
_collect_figures(c, out)
|
|
||||||
|
|
||||||
|
|
||||||
func _fallback_stand(out: PackedVector3Array) -> void:
|
|
||||||
var placed := 0
|
var placed := 0
|
||||||
for row in fallback_rows:
|
for row in fallback_rows:
|
||||||
var radius := fallback_inner_radius + float(row) * fallback_row_step
|
var radius := fallback_inner_radius + float(row) * fallback_row_step
|
||||||
@@ -133,5 +113,7 @@ func _fallback_stand(out: PackedVector3Array) -> void:
|
|||||||
if placed >= fallback_count:
|
if placed >= fallback_count:
|
||||||
return
|
return
|
||||||
var ang := TAU * float(s) / float(seats_in_row)
|
var ang := TAU * float(s) / float(seats_in_row)
|
||||||
out.append(Vector3(cos(ang) * radius, y, sin(ang) * radius))
|
var pos := Vector3(cos(ang) * radius, y, sin(ang) * radius)
|
||||||
|
positions.append(pos)
|
||||||
|
facings.append(-pos) # face the ring centre (origin)
|
||||||
placed += 1
|
placed += 1
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
@tool
|
||||||
|
extends Node3D
|
||||||
|
class_name CrowdMarker
|
||||||
|
## A designer-placed spot for one billboard spectator. Drop these into a level in the
|
||||||
|
## editor; crowd_billboards.gd harvests every marker (via the &"crowd_marker" group) into
|
||||||
|
## its MultiMesh at runtime, using each marker's global position and chosen facing.
|
||||||
|
##
|
||||||
|
## The marker draws a lightweight preview (a slab standing in for the sprite + a forward
|
||||||
|
## arrow) so its position and orientation are visible in the viewport. That preview is
|
||||||
|
## editor-only and never serialised into the scene, so it costs nothing at runtime.
|
||||||
|
|
||||||
|
## The six selectable facings, one every 60° about the Y axis. DEG_0 faces the quad's
|
||||||
|
## front (+Z) along world +Z; increasing values rotate clockwise looking down.
|
||||||
|
enum Orientation {
|
||||||
|
DEG_0,
|
||||||
|
DEG_60,
|
||||||
|
DEG_120,
|
||||||
|
DEG_180,
|
||||||
|
DEG_240,
|
||||||
|
DEG_300,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ORIENTATION_STEPS := 6
|
||||||
|
## Metres — matches crowd_billboards.figure_height so the editor preview reads true to scale.
|
||||||
|
const PREVIEW_HEIGHT := 2.0
|
||||||
|
|
||||||
|
@export var orientation := Orientation.DEG_0:
|
||||||
|
set(value):
|
||||||
|
orientation = value
|
||||||
|
_apply_orientation()
|
||||||
|
|
||||||
|
var _preview: Node3D
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group(&"crowd_marker")
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
_build_preview()
|
||||||
|
_apply_orientation()
|
||||||
|
|
||||||
|
|
||||||
|
# Facing about Y for the current orientation: 0, 60, 120 … degrees as radians.
|
||||||
|
func facing_yaw() -> float:
|
||||||
|
return float(orientation) * (TAU / float(ORIENTATION_STEPS))
|
||||||
|
|
||||||
|
|
||||||
|
# Unit forward the spectator's front (+Z) points along, on the horizontal plane.
|
||||||
|
func facing_dir() -> Vector3:
|
||||||
|
var a := facing_yaw()
|
||||||
|
return Vector3(sin(a), 0.0, cos(a))
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_orientation() -> void:
|
||||||
|
if _preview != null:
|
||||||
|
_preview.rotation = Vector3(0.0, facing_yaw(), 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
# Editor-only preview. Children are added without an owner so they are excluded from the
|
||||||
|
# saved .tscn (Godot only serialises nodes owned by the scene root).
|
||||||
|
func _build_preview() -> void:
|
||||||
|
_preview = Node3D.new()
|
||||||
|
add_child(_preview)
|
||||||
|
|
||||||
|
var aspect := 0.5
|
||||||
|
var slab := MeshInstance3D.new()
|
||||||
|
var quad := QuadMesh.new()
|
||||||
|
quad.size = Vector2(PREVIEW_HEIGHT * aspect, PREVIEW_HEIGHT)
|
||||||
|
quad.center_offset = Vector3(0.0, PREVIEW_HEIGHT * 0.5, 0.0) # stand on the marker
|
||||||
|
slab.mesh = quad
|
||||||
|
slab.material_override = _preview_material(Color(0.3, 0.7, 1.0, 0.35))
|
||||||
|
_preview.add_child(slab)
|
||||||
|
|
||||||
|
var arrow := MeshInstance3D.new()
|
||||||
|
var shaft := BoxMesh.new()
|
||||||
|
shaft.size = Vector3(0.08, 0.08, PREVIEW_HEIGHT * 0.5)
|
||||||
|
arrow.mesh = shaft
|
||||||
|
# Push the shaft out along +Z (the facing) and up to chest height.
|
||||||
|
arrow.position = Vector3(0.0, PREVIEW_HEIGHT * 0.5, PREVIEW_HEIGHT * 0.25)
|
||||||
|
arrow.material_override = _preview_material(Color(1.0, 0.85, 0.2, 0.9))
|
||||||
|
_preview.add_child(arrow)
|
||||||
|
|
||||||
|
|
||||||
|
func _preview_material(col: Color) -> StandardMaterial3D:
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = col
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
return mat
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://70j3ks1emhkd
|
||||||
@@ -10,6 +10,7 @@ extends Node
|
|||||||
##
|
##
|
||||||
## Toggle from the console:
|
## Toggle from the console:
|
||||||
## show_collisions · show_hitboxes · show_bones · show_raycasts (true / false)
|
## show_collisions · show_hitboxes · show_bones · show_raycasts (true / false)
|
||||||
|
## show_hitboxes also surfaces the bull's and bear's body capsules, not just Area3Ds
|
||||||
## show_states — floating AI-state tags + velocity vectors over each matador
|
## show_states — floating AI-state tags + velocity vectors over each matador
|
||||||
## show_animation_name — floating current-clip tag over every AnimationPlayer host
|
## show_animation_name — floating current-clip tag over every AnimationPlayer host
|
||||||
## show_stats — 2D corner readout: fps / frame ms / draw calls / matador tally
|
## show_stats — 2D corner readout: fps / frame ms / draw calls / matador tally
|
||||||
@@ -421,7 +422,10 @@ func _collect(node: Node, col: bool, hit: bool, bone: bool, anim: bool, wanted:
|
|||||||
# Ragdoll bone colliders (PhysicalBone3D) are excluded — use show_bones for
|
# Ragdoll bone colliders (PhysicalBone3D) are excluded — use show_bones for
|
||||||
# the skeleton; otherwise every corpse floods the view with capsules.
|
# the skeleton; otherwise every corpse floods the view with capsules.
|
||||||
var is_body := parent is PhysicsBody3D and not (parent is PhysicalBone3D)
|
var is_body := parent is PhysicsBody3D and not (parent is PhysicalBone3D)
|
||||||
if (is_area and hit) or (is_body and col):
|
# The bull's and bear's body capsules are combat volumes too, so they also
|
||||||
|
# surface under show_hitboxes (not just show_collisions).
|
||||||
|
var is_combatant := is_body and (parent.is_in_group(&"player") or parent.is_in_group(&"bear"))
|
||||||
|
if (is_area and hit) or (is_body and col) or (is_combatant and hit):
|
||||||
wanted[cs.get_instance_id()] = [cs, is_area]
|
wanted[cs.get_instance_id()] = [cs, is_area]
|
||||||
for child in node.get_children():
|
for child in node.get_children():
|
||||||
_collect(child, col, hit, bone, anim, wanted)
|
_collect(child, col, hit, bone, anim, wanted)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ var _values: Dictionary = {}
|
|||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_register_all()
|
_register_all()
|
||||||
load_saved()
|
|
||||||
|
|
||||||
|
|
||||||
func _register_all() -> void:
|
func _register_all() -> void:
|
||||||
@@ -46,12 +45,12 @@ func _register_all() -> void:
|
|||||||
_reg_f("Dust", "dust_explosiveness", 0.35, 0.0, 1.0)
|
_reg_f("Dust", "dust_explosiveness", 0.35, 0.0, 1.0)
|
||||||
_reg_f("Dust", "dust_spread", 45.0, 5.0, 90.0)
|
_reg_f("Dust", "dust_spread", 45.0, 5.0, 90.0)
|
||||||
_reg_f("Dust", "dust_gravity_y", -1.5,-15.0, 0.0)
|
_reg_f("Dust", "dust_gravity_y", -1.5,-15.0, 0.0)
|
||||||
_reg_f("Dust", "walk_scale_min", 0.35, 0.05, 3.0)
|
_reg_f("Dust", "walk_scale_min", 0.20, 0.05, 3.0)
|
||||||
_reg_f("Dust", "walk_scale_max", 0.65, 0.05, 3.0)
|
_reg_f("Dust", "walk_scale_max", 0.40, 0.05, 3.0)
|
||||||
_reg_f("Dust", "walk_vel_min", 0.8, 0.1, 12.0)
|
_reg_f("Dust", "walk_vel_min", 0.8, 0.1, 12.0)
|
||||||
_reg_f("Dust", "walk_vel_max", 1.8, 0.1, 12.0)
|
_reg_f("Dust", "walk_vel_max", 1.8, 0.1, 12.0)
|
||||||
_reg_f("Dust", "charge_scale_min", 1.0, 0.05, 6.0)
|
_reg_f("Dust", "charge_scale_min", 0.35, 0.05, 6.0)
|
||||||
_reg_f("Dust", "charge_scale_max", 2.2, 0.05, 6.0)
|
_reg_f("Dust", "charge_scale_max", 0.70, 0.05, 6.0)
|
||||||
_reg_f("Dust", "charge_vel_min", 2.8, 0.1, 20.0)
|
_reg_f("Dust", "charge_vel_min", 2.8, 0.1, 20.0)
|
||||||
_reg_f("Dust", "charge_vel_max", 5.5, 0.1, 20.0)
|
_reg_f("Dust", "charge_vel_max", 5.5, 0.1, 20.0)
|
||||||
# ── Tail ──────────────────────────────────────────────────────────────────
|
# ── Tail ──────────────────────────────────────────────────────────────────
|
||||||
@@ -99,16 +98,12 @@ func _register_all() -> void:
|
|||||||
_reg_f("Camera", "cam_drag_speed", 3.5, 0.5, 20.0)
|
_reg_f("Camera", "cam_drag_speed", 3.5, 0.5, 20.0)
|
||||||
_reg_f("Camera", "cam_follow_lerp", 10.0, 1.0, 40.0)
|
_reg_f("Camera", "cam_follow_lerp", 10.0, 1.0, 40.0)
|
||||||
# ── Matador ───────────────────────────────────────────────────────────────
|
# ── Matador ───────────────────────────────────────────────────────────────
|
||||||
_reg_f("Matador", "mat_spawn_count", 1.0, 1.0, 30.0, 1.0)
|
# Wave system: matadors enter the arena in escalating waves (1 → 2 → … → N where N
|
||||||
# Hydra split: a slain matador is replaced on the spot by mat_split_factor fresh
|
# is the number of placed spawn points). mat_spawn_stagger is the delay between each
|
||||||
# matadors, so the horde doubles each generation (1 → 2 → 4 → 8 …) at the default
|
# matador leaping in within a wave; mat_wave_pause is the breather after a wave clears.
|
||||||
# 2. Capped at mat_split_generations deep so the arena stays winnable (factor <2 or
|
_reg_f("Matador", "mat_wave_size", 8.0, 1, 50)
|
||||||
# generations 0 = no splitting, the classic one-and-done arena).
|
_reg_f("Matador", "mat_spawn_stagger", 0.4, 0.0, 3.0, 0.05)
|
||||||
_reg_f("Matador", "mat_split_factor", 2.0, 1.0, 6.0, 1.0)
|
_reg_f("Matador", "mat_wave_pause", 2.0, 0.0, 8.0, 0.25)
|
||||||
_reg_f("Matador", "mat_split_generations", 3.0, 0.0, 6.0, 1.0)
|
|
||||||
# The bull wins outright once it has slain this many matadors, even if the hydra
|
|
||||||
# still has bodies on the sand. 0 = disabled (win only when the horde dies out)
|
|
||||||
_reg_f("Matador", "mat_win_kills", 21.0, 0.0, 200.0, 1.0)
|
|
||||||
_reg_f("Matador", "mat_walk_speed", 5.5, 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)
|
_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
|
# Radius of the inner fighting floor: bull + matadors spawn on this ring (so
|
||||||
@@ -139,7 +134,7 @@ func _register_all() -> void:
|
|||||||
_reg_f("Matador", "mat_attack_range", 8.0, 2.0, 20.0)
|
_reg_f("Matador", "mat_attack_range", 8.0, 2.0, 20.0)
|
||||||
_reg_f("Matador", "mat_attack_speed", 7.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_duration", 1.8, 0.5, 5.0, 0.1)
|
||||||
_reg_f("Matador", "mat_attack_min_dist", 3.2, 0.5, 6.0, 0.1)
|
_reg_f("Matador", "mat_attack_min_dist", 4.2, 0.5, 6.0, 0.1)
|
||||||
# Forward drive during the opening of a melee swing — turns the stationary stab
|
# 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.
|
# into an estocada lunge so the blade covers ground toward the bull.
|
||||||
_reg_f("Matador", "mat_lunge_speed", 13.0, 0.0, 20.0)
|
_reg_f("Matador", "mat_lunge_speed", 13.0, 0.0, 20.0)
|
||||||
@@ -153,7 +148,7 @@ func _register_all() -> void:
|
|||||||
# Stops "sword's off to the side but I still died" — you're only gored when the
|
# Stops "sword's off to the side but I still died" — you're only gored when the
|
||||||
# blade is actually presented at you.
|
# blade is actually presented at you.
|
||||||
_reg_f("Matador", "mat_stab_arc", 55.0, 5.0, 180.0, 5.0)
|
_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_chance", 0.8, 0.0, 1.0, 0.05)
|
||||||
_reg_f("Matador", "mat_roll_duration", 0.45, 0.1, 1.5, 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 —
|
# Playback rate of the Draw_weapon clip used for both drawing and sheathing —
|
||||||
# higher = snappier unholster/holster.
|
# higher = snappier unholster/holster.
|
||||||
@@ -190,24 +185,98 @@ func _register_all() -> void:
|
|||||||
_reg_f("Matador", "mat_taunt_hold", 8.0, 1.0, 20.0, 0.5)
|
_reg_f("Matador", "mat_taunt_hold", 8.0, 1.0, 20.0, 0.5)
|
||||||
# Small so it smooths dithering without locking the matador into idling.
|
# 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)
|
_reg_f("Matador", "mat_w_commit", 0.15, 0.0, 2.0, 0.05)
|
||||||
|
# ── Bear (boss) ─────────────────────────────────────────────────────────────
|
||||||
|
# A Dark-Souls-style boss with a learnable moveset. It soaks bear_max_hp clean hits
|
||||||
|
# (ram or ability), staggering on each, and only topples when worn down. From NEUTRAL
|
||||||
|
# it weighs its options by the range band to the bull and commits to one telegraphed
|
||||||
|
# routine (wind-up → active → recovery); each routine plays to the end (the recovery
|
||||||
|
# is the player's punish window). Below bear_enrage_frac HP it enters phase 2: faster
|
||||||
|
# tells and recovery (× bear_enrage_scale) and longer combo chains.
|
||||||
|
_reg_f("Bear", "bear_max_hp", 30.0, 1.0, 100.0, 1.0)
|
||||||
|
# HP fraction at which phase 2 ("Fury") triggers, and how much it multiplies every
|
||||||
|
# wind-up / recovery timer (lower = snappier, more dangerous).
|
||||||
|
_reg_f("Bear", "bear_enrage_frac", 0.4, 0.0, 1.0, 0.05)
|
||||||
|
_reg_f("Bear", "bear_enrage_scale", 0.7, 0.3, 1.0, 0.05)
|
||||||
|
# Range bands (m) that pick the moveset: ≤close it strikes (swipe / smash), ≤mid it
|
||||||
|
# walks in to close the gap (stalk) or leaps, beyond it lopes in (prowl) or leaps.
|
||||||
|
# close is deliberately generous — the bear commits to melee "from a bit further" and
|
||||||
|
# steps in during the swing (see bear_lunge_speed) rather than needing to be on top.
|
||||||
|
_reg_f("Bear", "bear_close_range", 5.0, 1.0, 12.0, 0.1)
|
||||||
|
_reg_f("Bear", "bear_mid_range", 9.0, 3.0, 20.0, 0.5)
|
||||||
|
# Beat spent in NEUTRAL sizing up the bull between routines — kept short so the bear
|
||||||
|
# flows straight from one action into the next instead of pausing (raise for a more
|
||||||
|
# hesitant, readable boss; near-0 for relentless pressure). Shrinks again in phase 2.
|
||||||
|
_reg_f("Bear", "bear_think_time", 0.06, 0.0, 1.0, 0.01)
|
||||||
|
# Locomotion: the all-fours prowl (close a big gap), the menacing 2-leg walk-in
|
||||||
|
# (stalk), the facing rate while positioning/winding up, and the softer tracking rate
|
||||||
|
# a swing keeps on the bull mid-attack so it's harder to just walk out of.
|
||||||
|
_reg_f("Bear", "bear_run_speed", 11.0, 1.0, 20.0, 0.1)
|
||||||
|
_reg_f("Bear", "bear_stalk_speed", 5.5, 0.5, 12.0, 0.1)
|
||||||
|
_reg_f("Bear", "bear_turn_rate", 7.0, 1.0, 20.0, 0.5)
|
||||||
|
_reg_f("Bear", "bear_track_active", 3.5, 0.0, 12.0, 0.1)
|
||||||
|
# Forward step-in during a swing so a strike lands even when the bull is a touch out of
|
||||||
|
# reach (also carries the tracking); 0 = a purely stationary swing.
|
||||||
|
_reg_f("Bear", "bear_lunge_speed", 7.0, 0.0, 16.0, 0.5)
|
||||||
|
# Playback multiplier for the swipe/smash clips — >1 makes the punches snappier.
|
||||||
|
_reg_f("Bear", "bear_attack_anim_speed", 1.35, 0.5, 3.0, 0.05)
|
||||||
|
# Half-arc (deg) the bear must be facing the bull within for a directional swing to
|
||||||
|
# connect; slams use their own radius instead of an arc.
|
||||||
|
_reg_f("Bear", "bear_hit_arc", 70.0, 5.0, 180.0, 5.0)
|
||||||
|
# Double Swipe (ONE_TWO_HIT) — bread-and-butter close combo, two quick hits.
|
||||||
|
_reg_f("Bear", "bear_swipe_windup", 0.3, 0.05, 2.0, 0.05)
|
||||||
|
_reg_f("Bear", "bear_swipe_recover", 0.35, 0.05, 3.0, 0.05)
|
||||||
|
_reg_f("Bear", "bear_swipe_reach", 4.2, 1.0, 8.0, 0.1)
|
||||||
|
# Overhead Smash (SMASH_HIT) — slower tell, biggest punish window, AoE on impact.
|
||||||
|
_reg_f("Bear", "bear_smash_windup", 0.6, 0.1, 3.0, 0.05)
|
||||||
|
_reg_f("Bear", "bear_smash_recover", 0.75, 0.1, 4.0, 0.05)
|
||||||
|
_reg_f("Bear", "bear_smash_radius", 4.2, 1.0, 8.0, 0.1)
|
||||||
|
# Leap Slam (JUMP_SMASH, 47 frames @30fps) — timed to the clip by FRAME: it plays the
|
||||||
|
# crouch grounded, springs at the takeoff frame, and the arc is solved so the paws hit
|
||||||
|
# ground exactly on the impact frame (25), leaving the tail frames to settle on the
|
||||||
|
# ground — no finishing the anim in mid-air. The arc uses its own gravity so peak height
|
||||||
|
# and airtime are independent: bear_leap_height is the peak reached in that fixed airtime.
|
||||||
|
_reg_f("Bear", "bear_leap_takeoff_frame", 6.0, 0.0, 46.0, 1.0)
|
||||||
|
_reg_f("Bear", "bear_leap_impact_frame", 25.0, 1.0, 47.0, 1.0)
|
||||||
|
_reg_f("Bear", "bear_leap_height", 2.6, 0.5, 8.0, 0.1)
|
||||||
|
_reg_f("Bear", "bear_leap_recover", 0.35, 0.0, 4.0, 0.05)
|
||||||
|
_reg_f("Bear", "bear_leap_speed", 14.0, 4.0, 30.0, 0.5)
|
||||||
|
_reg_f("Bear", "bear_slam_radius", 4.0, 1.0, 8.0, 0.1)
|
||||||
|
# Stalk (WALK_ON2LEGS) — how long it will keep walking the bull down before re-deciding.
|
||||||
|
_reg_f("Bear", "bear_stalk_time", 2.5, 0.0, 6.0, 0.1)
|
||||||
|
# Odds a close combo chains a follow-up smash after the swipe (phase 2 always chains).
|
||||||
|
_reg_f("Bear", "bear_combo_chance", 0.7, 0.0, 1.0, 0.05)
|
||||||
|
# Minimum bull speed (ram) that counts as a hit on the bear — a gentle nudge is
|
||||||
|
# shrugged off, only a real charge or an ability wounds it. (The bear itself never
|
||||||
|
# damages the bull by colliding — only its swings and slams hurt.)
|
||||||
|
_reg_f("Bear", "bear_hit_threshold", 6.0, 1.0, 30.0, 0.5)
|
||||||
|
# How long (s) the bear is rocked back and can't act after taking a hit.
|
||||||
|
_reg_f("Bear", "bear_stagger_time", 0.5, 0.0, 2.0, 0.05)
|
||||||
|
# Poise: after a stagger the bear shrugs off staggers for this long (hits still wound
|
||||||
|
# it), so it can't be perma-stunlocked — it gets a window to commit an attack that then
|
||||||
|
# rides its hyper-armour. 0 = no poise (stun-locks under sustained fire).
|
||||||
|
_reg_f("Bear", "bear_stagger_cd", 1.0, 0.0, 4.0, 0.05)
|
||||||
# ── Sword ─────────────────────────────────────────────────────────────────
|
# ── Sword ─────────────────────────────────────────────────────────────────
|
||||||
# Local seating of the sword in the right hand (drawn / fighting).
|
# Local seating of the sword in the right hand (drawn / fighting).
|
||||||
# Defaults are identity — the bone is oriented in Blender to seat the sword
|
# The blade model runs along its local +Z, but weapon_bone (like every rig bone)
|
||||||
# directly; these exist to fine-tune in-game without re-exporting.
|
# points down its local +Y — so identity leaves the blade jutting out sideways.
|
||||||
|
# rot_x = -90 maps the blade's +Z onto the bone's +Y so it thrusts straight along
|
||||||
|
# the bone; rot_y is then the free roll (crossguard tilt) about the blade axis.
|
||||||
_reg_f("Sword", "sword_pos_x", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_pos_x", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_pos_y", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_pos_y", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_pos_z", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_pos_z", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_rot_x", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rot_x", -90.0, -180.0, 180.0, 1.0)
|
||||||
_reg_f("Sword", "sword_rot_y", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rot_y", 0.0, -180.0, 180.0, 1.0)
|
||||||
_reg_f("Sword", "sword_rot_z", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rot_z", 0.0, -180.0, 180.0, 1.0)
|
||||||
# Local seating of the sword in the hip holster (sheathed / wandering).
|
# Local seating of the sword in the hip holster (sheathed / wandering).
|
||||||
# Independent of the hand grip so each pose can be tuned without affecting the
|
# Independent of the hand grip so each pose can be tuned without affecting the
|
||||||
# other. Defaults angle the blade down along the matador's side.
|
# other. weapon_rest_bone sits on the left hip; these angle the blade down and
|
||||||
|
# out along the OUTSIDE of the left thigh (rot_x/rot_y aim the blade, rot_z rolls
|
||||||
|
# it about its own axis) so it trails back beside the leg instead of clipping it.
|
||||||
_reg_f("Sword", "sword_rest_pos_x", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_rest_pos_x", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_rest_pos_y", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_rest_pos_y", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_rest_pos_z", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_rest_pos_z", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_rest_rot_x", -120.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rest_rot_x", -110.0, -180.0, 180.0, 1.0)
|
||||||
_reg_f("Sword", "sword_rest_rot_y", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rest_rot_y", -133.0, -180.0, 180.0, 1.0)
|
||||||
_reg_f("Sword", "sword_rest_rot_z", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rest_rot_z", 0.0, -180.0, 180.0, 1.0)
|
||||||
# ── Spear throw (matador ranged attack) ──────────────────────────────────
|
# ── Spear throw (matador ranged attack) ──────────────────────────────────
|
||||||
# Local seating of the spear in the throwing hand during the wind-up. Tune to
|
# Local seating of the spear in the throwing hand during the wind-up. Tune to
|
||||||
@@ -238,10 +307,10 @@ func _register_all() -> void:
|
|||||||
_reg_f("Sword", "mat_throw_arm_release", -90.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)
|
_reg_f("Sword", "mat_throw_elbow", 80.0,-180.0, 180.0, 1.0)
|
||||||
# ── Abilities ─────────────────────────────────────────────────────────────
|
# ── Abilities ─────────────────────────────────────────────────────────────
|
||||||
_reg_f("Abilities", "kick_cooldown", 1.5, 0.2, 10.0, 0.1)
|
_reg_f("Abilities", "kick_cooldown", 2, 0.2, 600, 0.1)
|
||||||
_reg_f("Abilities", "kick_range", 6.0, 0.5, 8.0, 0.1)
|
_reg_f("Abilities", "kick_range", 6.0, 0.5, 8.0, 0.1)
|
||||||
_reg_f("Abilities", "kick_strength", 12.0, 1.0, 30.0)
|
_reg_f("Abilities", "kick_strength", 12.0, 1.0, 30.0)
|
||||||
_reg_f("Abilities", "slam_cooldown", 4.0, 0.5, 15.0, 0.1)
|
_reg_f("Abilities", "slam_cooldown", 12, 0.5, 600, 0.1)
|
||||||
_reg_f("Abilities", "slam_jump_velocity", 7.0, 2.0, 20.0)
|
_reg_f("Abilities", "slam_jump_velocity", 7.0, 2.0, 20.0)
|
||||||
# Descent gravity multiplier — higher = faster crash, less airtime.
|
# Descent gravity multiplier — higher = faster crash, less airtime.
|
||||||
_reg_f("Abilities", "slam_fall_mult", 3.0, 1.0, 8.0, 0.5)
|
_reg_f("Abilities", "slam_fall_mult", 3.0, 1.0, 8.0, 0.5)
|
||||||
@@ -250,7 +319,7 @@ func _register_all() -> void:
|
|||||||
_reg_f("Abilities", "slam_launch_up", 7.0, 0.0, 15.0, 0.5)
|
_reg_f("Abilities", "slam_launch_up", 7.0, 0.0, 15.0, 0.5)
|
||||||
_reg_f("Abilities", "slam_range", 4.0, 1.0, 10.0, 0.1)
|
_reg_f("Abilities", "slam_range", 4.0, 1.0, 10.0, 0.1)
|
||||||
_reg_f("Abilities", "slam_strength", 14.0, 1.0, 30.0)
|
_reg_f("Abilities", "slam_strength", 14.0, 1.0, 30.0)
|
||||||
_reg_f("Abilities", "dash_cooldown", 0.5, 0.1, 10.0, 0.1)
|
_reg_f("Abilities", "dash_cooldown", 3, 0.1, 600.0, 0.1)
|
||||||
_reg_f("Abilities", "dash_speed", 66.0, 5.0, 200.0)
|
_reg_f("Abilities", "dash_speed", 66.0, 5.0, 200.0)
|
||||||
# Exponential rate the dash bleeds from dash_speed back to cruise (max_speed).
|
# Exponential rate the dash bleeds from dash_speed back to cruise (max_speed).
|
||||||
# Higher = a snappier bullet that dies off quicker; lower = a longer glide.
|
# Higher = a snappier bullet that dies off quicker; lower = a longer glide.
|
||||||
@@ -261,7 +330,7 @@ func _register_all() -> void:
|
|||||||
# closest matador; on slamming one the ball POPS it (launched skyward via
|
# closest matador; on slamming one the ball POPS it (launched skyward via
|
||||||
# roll_knockup) then CHAINS to the next closest, up to roll_chain_count times.
|
# roll_knockup) then CHAINS to the next closest, up to roll_chain_count times.
|
||||||
# roll_turn is the homing turn rate. When no chain targets remain, the roll ends.
|
# roll_turn is the homing turn rate. When no chain targets remain, the roll ends.
|
||||||
_reg_f("Roll", "roll_cooldown", 3.0, 0.5, 15.0, 0.1)
|
_reg_f("Roll", "roll_cooldown", 20, 0.5, 600, 0.1)
|
||||||
_reg_f("Roll", "roll_duration", 2.5, 0.5, 6.0, 0.1) # max roll time
|
_reg_f("Roll", "roll_duration", 2.5, 0.5, 6.0, 0.1) # max roll time
|
||||||
_reg_f("Roll", "roll_chain_count", 2.0, 0.0, 8.0, 1.0) # extra matadors chained after the first
|
_reg_f("Roll", "roll_chain_count", 2.0, 0.0, 8.0, 1.0) # extra matadors chained after the first
|
||||||
_reg_f("Roll", "roll_base_speed", 28.0, 5.0, 120.0) # speed at the start of the roll
|
_reg_f("Roll", "roll_base_speed", 28.0, 5.0, 120.0) # speed at the start of the roll
|
||||||
@@ -281,8 +350,8 @@ func _register_all() -> void:
|
|||||||
# a uniform out of bounds.
|
# a uniform out of bounds.
|
||||||
_reg_b("PS1", "show_ps1", true)
|
_reg_b("PS1", "show_ps1", true)
|
||||||
_reg_f("PS1", "ps1_pixel_scale", 4.0, 1.0, 8.0, 1.0)
|
_reg_f("PS1", "ps1_pixel_scale", 4.0, 1.0, 8.0, 1.0)
|
||||||
_reg_f("PS1", "ps1_color_depth", 5.0, 2.0, 8.0, 1.0)
|
_reg_f("PS1", "ps1_color_depth", 4.5, 2.0, 8.0, 1.0)
|
||||||
_reg_f("PS1", "ps1_dither_strength", 0.8, 0.0, 2.0, 0.05)
|
_reg_f("PS1", "ps1_dither_strength", 1, 0.0, 2.0, 0.05)
|
||||||
_reg_f("PS1", "ps1_scanline_strength", 0.2, 0.0, 1.0, 0.05)
|
_reg_f("PS1", "ps1_scanline_strength", 0.2, 0.0, 1.0, 0.05)
|
||||||
_reg_f("PS1", "ps1_roll_speed", 3.0, 0.0, 10.0, 0.1)
|
_reg_f("PS1", "ps1_roll_speed", 3.0, 0.0, 10.0, 0.1)
|
||||||
_reg_f("PS1", "ps1_roll_strength", 1.0, 0.0, 1.0, 0.05)
|
_reg_f("PS1", "ps1_roll_strength", 1.0, 0.0, 1.0, 0.05)
|
||||||
@@ -299,6 +368,9 @@ func _register_all() -> void:
|
|||||||
# Force the on-screen touch controls (joystick + ability buttons) on even on a
|
# Force the on-screen touch controls (joystick + ability buttons) on even on a
|
||||||
# desktop build — otherwise they appear only when a touchscreen is detected.
|
# desktop build — otherwise they appear only when a touchscreen is detected.
|
||||||
_reg_b("Touch", "force_touch_controls", false)
|
_reg_b("Touch", "force_touch_controls", false)
|
||||||
|
# On-screen readout of live touch state (event count, last position, active zone) so
|
||||||
|
# touch problems can be diagnosed on the actual device, where no console is reachable.
|
||||||
|
_reg_b("Touch", "touch_debug", false)
|
||||||
|
|
||||||
|
|
||||||
func _reg_f(section: String, key: String, default: float,
|
func _reg_f(section: String, key: String, default: float,
|
||||||
@@ -349,11 +421,54 @@ func get_all() -> Dictionary:
|
|||||||
|
|
||||||
|
|
||||||
func save() -> void:
|
func save() -> void:
|
||||||
var cfg := ConfigFile.new()
|
var path := "res://debug_params.gd"
|
||||||
|
var file := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
push_error("DP.save: cannot open " + path)
|
||||||
|
return
|
||||||
|
var src := file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
|
||||||
for key: String in _params:
|
for key: String in _params:
|
||||||
var p: Dictionary = _params[key]
|
var p: Dictionary = _params[key]
|
||||||
cfg.set_value(p["section"], key, p["value"])
|
if p["value"] == p["default"]:
|
||||||
cfg.save(SAVE_PATH)
|
continue
|
||||||
|
var re := RegEx.new()
|
||||||
|
var literal := ""
|
||||||
|
if p["type"] == TYPE_FLOAT:
|
||||||
|
re.compile('(_reg_f\\("[^"]+",\\s*"%s",\\s*)([-\\d.e]+)' % key)
|
||||||
|
literal = _fmt_float(p["value"] as float)
|
||||||
|
else:
|
||||||
|
re.compile('(_reg_b\\("[^"]+",\\s*"%s",\\s*)(true|false)' % key)
|
||||||
|
literal = "true" if bool(p["value"]) else "false"
|
||||||
|
var m := re.search(src)
|
||||||
|
if m == null:
|
||||||
|
push_warning("DP.save: no _reg_ line found for '%s' — skipped" % key)
|
||||||
|
continue
|
||||||
|
# Splice the value in by index rather than RegEx.sub(): sub()'s replacement
|
||||||
|
# string parses "$n" as a backreference, so "$1"+"12.5" becomes "$112.5"
|
||||||
|
# (group 112 → PCRE2 error → mangled output). Indexed splice inserts the
|
||||||
|
# literal verbatim, exactly the value the user entered.
|
||||||
|
src = src.substr(0, m.get_start(2)) + literal + src.substr(m.get_end(2))
|
||||||
|
p["default"] = p["value"]
|
||||||
|
|
||||||
|
var wfile := FileAccess.open(path, FileAccess.WRITE)
|
||||||
|
if wfile == null:
|
||||||
|
push_error("DP.save: cannot write " + path)
|
||||||
|
return
|
||||||
|
wfile.store_string(src)
|
||||||
|
wfile.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Godot's String % operator has no "g" conversion — "%.6g" % v slips past the
|
||||||
|
# compiler (v is not const) but emits the literal "%.6g" at runtime, which would be
|
||||||
|
# written straight into the source. "%s" gives the shortest round-tripping form,
|
||||||
|
# matching the value the user entered; the guard keeps it a float literal.
|
||||||
|
func _fmt_float(v: float) -> String:
|
||||||
|
var s := "%s" % v
|
||||||
|
if not ("." in s or "e" in s):
|
||||||
|
s += ".0"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
func load_saved() -> void:
|
func load_saved() -> void:
|
||||||
|
|||||||
@@ -34,10 +34,28 @@ commit=${GIT_COMMIT}
|
|||||||
built=${BUILD_DATE}
|
built=${BUILD_DATE}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Cache-bust: give wasm/pck unique names so browsers never serve a stale build.
|
||||||
|
# index.js gets a ?v= query string; wasm/pck get renamed because the engine
|
||||||
|
# fetches them internally using GODOT_CONFIG.executable as the base name.
|
||||||
|
STAMP="$GIT_COMMIT"
|
||||||
|
cp "$BUILD_DIR/index.wasm" "$BUILD_DIR/index.${STAMP}.wasm"
|
||||||
|
cp "$BUILD_DIR/index.wasm.gz" "$BUILD_DIR/index.${STAMP}.wasm.gz"
|
||||||
|
cp "$BUILD_DIR/index.pck" "$BUILD_DIR/index.${STAMP}.pck"
|
||||||
|
|
||||||
|
sed -i \
|
||||||
|
-e "s|src=\"index\.js\"|src=\"index.js?v=${STAMP}\"|" \
|
||||||
|
-e "s|\"executable\":\"index\"|\"executable\":\"index.${STAMP}\"|" \
|
||||||
|
-e "s|\"index\.pck\"|\"index.${STAMP}.pck\"|g" \
|
||||||
|
-e "s|\"index\.wasm\"|\"index.${STAMP}.wasm\"|g" \
|
||||||
|
"$BUILD_DIR/index.html"
|
||||||
|
|
||||||
|
# Remove previous build's versioned assets from the server before uploading.
|
||||||
|
ssh "$SERVER_USER@$SERVER_HOST" "find $SERVER_PATH -maxdepth 1 -name 'index.*.wasm' -o -name 'index.*.wasm.gz' -o -name 'index.*.pck' | xargs -r rm -f"
|
||||||
|
|
||||||
scp "$BUILD_DIR/index.html" \
|
scp "$BUILD_DIR/index.html" \
|
||||||
"$BUILD_DIR/index.js" \
|
"$BUILD_DIR/index.js" \
|
||||||
"$BUILD_DIR/index.wasm.gz" \
|
"$BUILD_DIR/index.${STAMP}.wasm.gz" \
|
||||||
"$BUILD_DIR/index.pck" \
|
"$BUILD_DIR/index.${STAMP}.pck" \
|
||||||
"$BUILD_DIR/index.audio.worklet.js" \
|
"$BUILD_DIR/index.audio.worklet.js" \
|
||||||
"$BUILD_DIR/index.audio.position.worklet.js" \
|
"$BUILD_DIR/index.audio.position.worklet.js" \
|
||||||
"$BUILD_DIR/index.icon.png" \
|
"$BUILD_DIR/index.icon.png" \
|
||||||
@@ -46,4 +64,4 @@ scp "$BUILD_DIR/index.html" \
|
|||||||
"$BUILD_DIR/version.txt" \
|
"$BUILD_DIR/version.txt" \
|
||||||
"$SERVER_USER@$SERVER_HOST:$SERVER_PATH/"
|
"$SERVER_USER@$SERVER_HOST:$SERVER_PATH/"
|
||||||
|
|
||||||
echo "deployed ${VERSION} to $SERVER_HOST:$SERVER_PATH"
|
echo "deployed ${VERSION} (${STAMP}) to $SERVER_HOST:$SERVER_PATH"
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
|
[runnable_presets]
|
||||||
|
|
||||||
|
Web="Web"
|
||||||
|
"Windows Desktop"="Windows Desktop"
|
||||||
|
|
||||||
[preset.0]
|
[preset.0]
|
||||||
|
|
||||||
name="Web"
|
name="Web"
|
||||||
platform="Web"
|
platform="Web"
|
||||||
runnable=true
|
|
||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
include_filter=""
|
include_filter=""
|
||||||
exclude_filter=""
|
exclude_filter=""
|
||||||
export_path="build/web/index.html"
|
export_path="build/web/index.html"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
encrypt_pck=false
|
encrypt_pck=false
|
||||||
encrypt_directory=false
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
[preset.0.options]
|
[preset.0.options]
|
||||||
|
|
||||||
@@ -21,7 +33,7 @@ custom_template/release=""
|
|||||||
variant/extensions_support=false
|
variant/extensions_support=false
|
||||||
variant/thread_support=true
|
variant/thread_support=true
|
||||||
vram_texture_compression/for_desktop=true
|
vram_texture_compression/for_desktop=true
|
||||||
vram_texture_compression/for_mobile=false
|
vram_texture_compression/for_mobile=true
|
||||||
html/export_icon=true
|
html/export_icon=true
|
||||||
html/custom_html_shell=""
|
html/custom_html_shell=""
|
||||||
html/head_include=""
|
html/head_include=""
|
||||||
@@ -29,6 +41,7 @@ html/canvas_resize_policy=2
|
|||||||
html/focus_canvas_on_start=true
|
html/focus_canvas_on_start=true
|
||||||
html/experimental_virtual_keyboard=false
|
html/experimental_virtual_keyboard=false
|
||||||
progressive_web_app/enabled=false
|
progressive_web_app/enabled=false
|
||||||
|
progressive_web_app/ensure_cross_origin_isolation_headers=true
|
||||||
progressive_web_app/offline_page=""
|
progressive_web_app/offline_page=""
|
||||||
progressive_web_app/display=1
|
progressive_web_app/display=1
|
||||||
progressive_web_app/orientation=0
|
progressive_web_app/orientation=0
|
||||||
@@ -36,3 +49,76 @@ progressive_web_app/icon_144x144=""
|
|||||||
progressive_web_app/icon_180x180=""
|
progressive_web_app/icon_180x180=""
|
||||||
progressive_web_app/icon_512x512=""
|
progressive_web_app/icon_512x512=""
|
||||||
progressive_web_app/background_color=Color(0, 0, 0, 1)
|
progressive_web_app/background_color=Color(0, 0, 0, 1)
|
||||||
|
threads/emscripten_pool_size=8
|
||||||
|
threads/godot_pool_size=4
|
||||||
|
|
||||||
|
[preset.1]
|
||||||
|
|
||||||
|
name="Windows Desktop"
|
||||||
|
platform="Windows Desktop"
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="../Export/Bullosseum_020.exe"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.1.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=1
|
||||||
|
binary_format/embed_pck=false
|
||||||
|
texture_format/s3tc_bptc=true
|
||||||
|
texture_format/etc2_astc=false
|
||||||
|
shader_baker/enabled=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
codesign/enable=false
|
||||||
|
codesign/timestamp=true
|
||||||
|
codesign/timestamp_server_url=""
|
||||||
|
codesign/digest_algorithm=1
|
||||||
|
codesign/description=""
|
||||||
|
codesign/custom_options=PackedStringArray()
|
||||||
|
application/modify_resources=true
|
||||||
|
application/icon=""
|
||||||
|
application/console_wrapper_icon=""
|
||||||
|
application/icon_interpolation=4
|
||||||
|
application/file_version=""
|
||||||
|
application/product_version=""
|
||||||
|
application/company_name=""
|
||||||
|
application/product_name=""
|
||||||
|
application/file_description=""
|
||||||
|
application/copyright=""
|
||||||
|
application/trademarks=""
|
||||||
|
application/export_angle=0
|
||||||
|
application/export_d3d12=0
|
||||||
|
application/d3d12_agility_sdk_multiarch=true
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||||
|
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||||
|
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||||
|
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||||
|
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||||
|
Start-ScheduledTask -TaskName godot_remote_debug
|
||||||
|
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||||
|
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||||
|
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||||
|
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||||
|
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ var _result_win: bool = false
|
|||||||
var _over_root: Control
|
var _over_root: Control
|
||||||
var _over_video: VideoStreamPlayer
|
var _over_video: VideoStreamPlayer
|
||||||
var _over_label: Label
|
var _over_label: Label
|
||||||
|
var _over_continue: Button
|
||||||
|
var _over_again: Button
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -119,7 +121,11 @@ func _build_bottom_hud() -> void:
|
|||||||
# Bull-head plate that sits behind the ability + health cluster. It's a sibling of the
|
# Bull-head plate that sits behind the ability + health cluster. It's a sibling of the
|
||||||
# cluster (not a parent) so it never constrains the cluster's layout; we size and place
|
# cluster (not a parent) so it never constrains the cluster's layout; we size and place
|
||||||
# it against the cluster's rect in _position_bull_bg.
|
# it against the cluster's rect in _position_bull_bg.
|
||||||
|
# Skipped on touch/phone: the on-screen thumb cluster already crowds the bottom edge, so
|
||||||
|
# the wide horned plate just adds clutter there.
|
||||||
func _build_bull_hud_bg() -> void:
|
func _build_bull_hud_bg() -> void:
|
||||||
|
if Controls.use_touch_ui():
|
||||||
|
return
|
||||||
_bull_bg = TextureRect.new()
|
_bull_bg = TextureRect.new()
|
||||||
if ResourceLoader.exists(_BULL_BG):
|
if ResourceLoader.exists(_BULL_BG):
|
||||||
_bull_bg.texture = load(_BULL_BG)
|
_bull_bg.texture = load(_BULL_BG)
|
||||||
@@ -210,7 +216,10 @@ func _find_spawner() -> void:
|
|||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if event is InputEventKey and event.pressed and not event.echo:
|
if event is InputEventKey and event.pressed and not event.echo:
|
||||||
if _game_over and event.physical_keycode in [KEY_ENTER, KEY_KP_ENTER]:
|
if _game_over and event.physical_keycode in [KEY_ENTER, KEY_KP_ENTER]:
|
||||||
_restart()
|
if _result_win:
|
||||||
|
_continue_to_lobby()
|
||||||
|
else:
|
||||||
|
_restart()
|
||||||
elif event.physical_keycode == KEY_TAB and not _game_over:
|
elif event.physical_keycode == KEY_TAB and not _game_over:
|
||||||
_toggle_controls()
|
_toggle_controls()
|
||||||
elif event.physical_keycode == KEY_ESCAPE:
|
elif event.physical_keycode == KEY_ESCAPE:
|
||||||
@@ -229,6 +238,17 @@ func _restart() -> void:
|
|||||||
get_tree().reload_current_scene()
|
get_tree().reload_current_scene()
|
||||||
|
|
||||||
|
|
||||||
|
# A cleared level advances the run and drops the player into the lobby hub: commit the
|
||||||
|
# win, flag the lobby as the active level, then reload the shell so it builds the lobby.
|
||||||
|
# (Choosing the next map node happens from the lobby via trigger areas, added later.)
|
||||||
|
func _continue_to_lobby() -> void:
|
||||||
|
get_tree().paused = false
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
Run.complete_current_level()
|
||||||
|
Run.go_to_lobby()
|
||||||
|
get_tree().change_scene_to_file(Run.active_level().scene_path)
|
||||||
|
|
||||||
|
|
||||||
func _toggle_controls() -> void:
|
func _toggle_controls() -> void:
|
||||||
_controls_visible = not _controls_visible
|
_controls_visible = not _controls_visible
|
||||||
_toggle_btn.text = "Hide controls" if _controls_visible else "Show controls"
|
_toggle_btn.text = "Hide controls" if _controls_visible else "Show controls"
|
||||||
@@ -240,7 +260,13 @@ func _toggle_controls() -> void:
|
|||||||
# joystick and ability buttons instead. Shares Controls.use_touch_ui() with that
|
# joystick and ability buttons instead. Shares Controls.use_touch_ui() with that
|
||||||
# overlay so exactly one control scheme shows on any given device.
|
# overlay so exactly one control scheme shows on any given device.
|
||||||
func _update_control_hints() -> void:
|
func _update_control_hints() -> void:
|
||||||
if _game_over or Controls.use_touch_ui():
|
var touch := Controls.use_touch_ui()
|
||||||
|
# The desktop tiles carry keyboard keycaps that mean nothing on a phone, and their
|
||||||
|
# STOP-filtered panels would sit over the joystick zone eating touches — touch_controls
|
||||||
|
# shows its own ability buttons instead, so drop the desktop bar on touch devices.
|
||||||
|
if _ability_bar != null:
|
||||||
|
_ability_bar.visible = not touch
|
||||||
|
if _game_over or touch:
|
||||||
_controls_panel.visible = false
|
_controls_panel.visible = false
|
||||||
_tab_hint.visible = false
|
_tab_hint.visible = false
|
||||||
else:
|
else:
|
||||||
@@ -301,6 +327,9 @@ func _show_game_over(win: bool, _cause: String = "") -> void:
|
|||||||
await get_tree().create_timer(_WIN_DELAY if win else _LOSS_DELAY, false, false, true).timeout
|
await get_tree().create_timer(_WIN_DELAY if win else _LOSS_DELAY, false, false, true).timeout
|
||||||
|
|
||||||
_over_label.text = "Bull Won!" if _result_win else "Matadors Won!"
|
_over_label.text = "Bull Won!" if _result_win else "Matadors Won!"
|
||||||
|
# A win advances the run (on to the map); a loss ends it (retry from the top).
|
||||||
|
_over_continue.visible = _result_win
|
||||||
|
_over_again.visible = not _result_win
|
||||||
|
|
||||||
var path := _WIN_VIDEO if _result_win else _LOSS_VIDEO
|
var path := _WIN_VIDEO if _result_win else _LOSS_VIDEO
|
||||||
if ResourceLoader.exists(path):
|
if ResourceLoader.exists(path):
|
||||||
@@ -371,9 +400,15 @@ func _build_game_over() -> void:
|
|||||||
row.offset_bottom = -48.0
|
row.offset_bottom = -48.0
|
||||||
_over_root.add_child(row)
|
_over_root.add_child(row)
|
||||||
|
|
||||||
var again := _make_button("Go Again (Enter)")
|
# Win → Continue to the run map; loss → Go Again from the top. Shown per-result in
|
||||||
again.pressed.connect(_restart)
|
# _show_game_over; both share the Enter shortcut.
|
||||||
row.add_child(again)
|
_over_continue = _make_button("Continue (Enter)")
|
||||||
|
_over_continue.pressed.connect(_continue_to_lobby)
|
||||||
|
row.add_child(_over_continue)
|
||||||
|
|
||||||
|
_over_again = _make_button("Go Again (Enter)")
|
||||||
|
_over_again.pressed.connect(_restart)
|
||||||
|
row.add_child(_over_again)
|
||||||
|
|
||||||
var menu := _make_button("Main Menu")
|
var menu := _make_button("Main Menu")
|
||||||
menu.pressed.connect(_return_to_menu)
|
menu.pressed.connect(_return_to_menu)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[gd_scene format=3 uid="uid://map_screen_v1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://levels/map_screen.gd" id="1_map"]
|
||||||
|
|
||||||
|
[node name="MapScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("1_map")
|
||||||
@@ -0,0 +1,773 @@
|
|||||||
|
[gd_scene format=3 uid="uid://co4tgvdpdte4v"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dio67r8ambe7h" path="res://Assets/arena_placeholder_v01.glb" id="2_nxogm"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cl54dt38yj2x" path="res://Assets/Arena.glb" id="3_fy5k1"]
|
||||||
|
[ext_resource type="Script" uid="uid://dou5ar6tkmng1" path="res://levels/matador_spawn.gd" id="3_spawn"]
|
||||||
|
[ext_resource type="Script" uid="uid://70j3ks1emhkd" path="res://crowd_marker.gd" id="9_agtuu"]
|
||||||
|
[ext_resource type="Script" uid="uid://cj0ytlkuavmf4" path="res://crowd_billboards.gd" id="11_crowd"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_ulcgi"]
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_73fnb"]
|
||||||
|
|
||||||
|
[node name="arena_level" type="Node3D" unique_id=701327120]
|
||||||
|
|
||||||
|
[node name="Node3D" type="Node3D" parent="." unique_id=1772505910]
|
||||||
|
|
||||||
|
[node name="StaticBody3D" type="StaticBody3D" parent="Node3D" unique_id=2083665760]
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Node3D/StaticBody3D" unique_id=1028681009]
|
||||||
|
transform = Transform3D(30, 0, 0, 0, 1, 0, 0, 0, 30, 0, -0.5, 0)
|
||||||
|
shape = SubResource("BoxShape3D_ulcgi")
|
||||||
|
|
||||||
|
[node name="arena_placeholder_v01" parent="Node3D" unique_id=173047470 instance=ExtResource("2_nxogm")]
|
||||||
|
|
||||||
|
[node name="Arena_draft" parent="Node3D/arena_placeholder_v01" index="0" unique_id=603097973]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="Wall" parent="Node3D/arena_placeholder_v01" index="1" unique_id=171701243]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" parent="Node3D/arena_placeholder_v01/Wall/StaticBody3D" parent_id_path=PackedInt32Array(173047470, 1793167121) index="0" unique_id=404958985]
|
||||||
|
transform = Transform3D(0.98309773, 0, 0, 0, 0.9558868, 0, 0, 0, 4.061706, 0.13596812, -0.091219574, -5.4371103e-09)
|
||||||
|
|
||||||
|
[node name="Arena2" parent="Node3D/arena_placeholder_v01" unique_id=1270130903 instance=ExtResource("3_fy5k1")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.017679214, 9.536743e-07, -0.013886452)
|
||||||
|
|
||||||
|
[node name="MatadorSpawner" type="Node3D" parent="." unique_id=1524912967]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 14)
|
||||||
|
script = ExtResource("3_spawn")
|
||||||
|
|
||||||
|
[node name="MatadorSpawner2" type="Node3D" parent="." unique_id=2081003535]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 3, 0)
|
||||||
|
script = ExtResource("3_spawn")
|
||||||
|
|
||||||
|
[node name="MatadorSpawner4" type="Node3D" parent="." unique_id=497566616]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 3, 0)
|
||||||
|
script = ExtResource("3_spawn")
|
||||||
|
|
||||||
|
[node name="MatadorSpawner3" type="Node3D" parent="." unique_id=618446747]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, -15)
|
||||||
|
script = ExtResource("3_spawn")
|
||||||
|
|
||||||
|
[node name="Lights" type="Node3D" parent="." unique_id=614769858]
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Lights" unique_id=960453715]
|
||||||
|
transform = Transform3D(0.01535992, 0.56825536, -0.82270896, 0.16280846, 0.8104039, 0.56279576, 0.9865382, -0.14258848, -0.08006904, -4.08134, 9.197985, 0.97087634)
|
||||||
|
light_energy = 0.4
|
||||||
|
light_indirect_energy = 0.767
|
||||||
|
shadow_blur = 0.47
|
||||||
|
|
||||||
|
[node name="SpotLight3D2" type="SpotLight3D" parent="Lights" unique_id=1654524086]
|
||||||
|
transform = Transform3D(0.904133, 0.30962402, -0.2944087, 0.3084627, 0.0037425472, 0.9512291, 0.2956252, -0.95085174, -0.0921237, -3.629381, 10.86347, -2.0124264)
|
||||||
|
light_energy = 16.0
|
||||||
|
light_indirect_energy = 26.095
|
||||||
|
distance_fade_enabled = true
|
||||||
|
spot_range = 32.90586
|
||||||
|
spot_angle = 73.93
|
||||||
|
|
||||||
|
[node name="SpotLight3D" type="SpotLight3D" parent="Lights" unique_id=1408554387]
|
||||||
|
transform = Transform3D(0.9763736, -9.445564e-09, 0.21608931, -0.20963383, 0.24260214, 0.9472053, -0.052423734, -0.9701259, 0.23687032, 4.0400305, 13.463301, 3.9410677)
|
||||||
|
visible = false
|
||||||
|
light_energy = 13.695
|
||||||
|
shadow_enabled = true
|
||||||
|
spot_range = 32.90586
|
||||||
|
|
||||||
|
[node name="SUN_DirectionalLight3D2" type="DirectionalLight3D" parent="Lights" unique_id=1840798568]
|
||||||
|
transform = Transform3D(0.45811793, 0.34843865, -0.81775206, 0.5316611, 0.62986195, 0.56622505, 0.7123655, -0.69416475, 0.10329977, 0, 8.980043, 0)
|
||||||
|
light_energy = 0.106
|
||||||
|
shadow_enabled = true
|
||||||
|
shadow_blur = 10.0
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1750284504]
|
||||||
|
environment = SubResource("Environment_73fnb")
|
||||||
|
|
||||||
|
[node name="Crowd" type="Node3D" parent="." unique_id=2013004589]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 14)
|
||||||
|
|
||||||
|
[node name="CrowdBillboards" type="MultiMeshInstance3D" parent="Crowd" unique_id=106730614]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -5, -14)
|
||||||
|
script = ExtResource("11_crowd")
|
||||||
|
|
||||||
|
[node name="Left" type="Node3D" parent="Crowd" unique_id=1621845484]
|
||||||
|
|
||||||
|
[node name="L_Step3" type="Node3D" parent="Crowd/Left" unique_id=623102737]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.592416, -1.045898, -23.088398)
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=578926054]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1610777090]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1861343773]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.809, 0.14058208, 1.755331)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1555035931]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1750314761]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=691141866]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.14058161, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker15" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1664612281]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -1.0940075, 0, -1.355114)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker16" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1677355487]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -3.6482267, 0.14058113, -3.5884771)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker18" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=63826938]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, 1.3811512, 0.14058113, 2.7079024)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker19" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=972576799]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -6.663862, 0.14058161, -5.0203266)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker20" type="Node3D" parent="Crowd/Left/L_Step3" unique_id=1082256967]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -9.743899, 0.14058161, -6.133005)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="L_Step4" type="Node3D" parent="Crowd/Left" unique_id=2012797300]
|
||||||
|
|
||||||
|
[node name="CrowdMarker2" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=2100000002]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, -0.0132164955, -23.088398)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker3" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=563466125]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.533139, -0.0132164955, -24.70021)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker4" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1529541292]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.021559, -0.013215542, -21.333067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker5" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=10514539]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.887294, -0.0132164955, -18.725811)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker6" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1864581651]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.740928, -0.013216019, -28.157803)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker7" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=937874303]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.6115, -0.013216019, -29.924221)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker8" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1255677084]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.2360125, -0.0132164955, -30.806953)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=2072287774]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 13.294765, -0.0132164955, -24.4567)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1158540011]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 11.827905, -0.0132164955, -26.068512)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1862689569]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 14.316324, -0.013215542, -22.701368)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=494798769]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 15.182059, -0.0132164955, -20.094112)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1508360812]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 9.035693, -0.013216019, -29.526104)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1811163868]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 4.9062653, -0.013216019, -31.292522)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker15" type="Node3D" parent="Crowd/Left/L_Step4" unique_id=1866931486]
|
||||||
|
transform = Transform3D(0.9725945, 0, 0.232508, 0, 1, 0, -0.232508, 0, 0.9725945, 7.489478, -0.0132164955, -29.721859)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="L_Step1" type="Node3D" parent="Crowd/Left" unique_id=1744694409]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 11.379745, -2.091795, -22.703432)
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Left/L_Step1" unique_id=1620001578]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Left/L_Step1" unique_id=1055150056]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Left/L_Step1" unique_id=717131032]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2287855, 0.14058208, 1.9909182)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Left/L_Step1" unique_id=1781479729]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Left/L_Step1" unique_id=1352193990]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Left/L_Step1" unique_id=743385499]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.14058161, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="L_Step5" type="Node3D" parent="Crowd/Left" unique_id=670288549]
|
||||||
|
transform = Transform3D(0.9967379, 0, -0.08070645, 0, 1, 0, 0.08070645, 0, 0.9967379, 11.637655, -3.0939364, -20.899355)
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=274558473]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=516605334]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.809, 0.14058208, 1.755331)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1577172273]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1757843488]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1120424592]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.14058161, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker15" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1819323657]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -1.0940075, 0, -1.355114)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker16" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=538915422]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -3.6482267, 0.14058113, -3.5884771)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker17" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1328913359]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -0.053198814, 0.14058208, 0.27350044)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker18" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=878163668]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, 1.3811512, 0.14058113, 2.7079024)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker19" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1568364605]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -6.663862, 0.14058161, -5.0203266)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker20" type="Node3D" parent="Crowd/Left/L_Step5" unique_id=1367833604]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -9.743899, 0.14058161, -6.133005)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="Back" type="Node3D" parent="Crowd" unique_id=638158299]
|
||||||
|
|
||||||
|
[node name="B_Step3" type="Node3D" parent="Crowd/Back" unique_id=174422561]
|
||||||
|
transform = Transform3D(0.098605186, 0, 0.99512655, 0, 1, 0, -0.99512655, 0, 0.098605186, -8.350439, -1.045898, -26.733604)
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Back/B_Step3" unique_id=1376565490]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Back/B_Step3" unique_id=2134315249]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Back/B_Step3" unique_id=434884711]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.809, 0.14058208, 1.755331)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Back/B_Step3" unique_id=1901106701]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Back/B_Step3" unique_id=1659701219]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Back/B_Step3" unique_id=469584679]
|
||||||
|
transform = Transform3D(0.99999994, 0, 0, 0, 1, 0, 0, 0, 0.99999994, -7.9328146, 0.14058161, -5.5702662)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="B_Step4" type="Node3D" parent="Crowd/Back" unique_id=792967648]
|
||||||
|
transform = Transform3D(0.09860524, 0, 0.9951266, 0, 1, 0, -0.9951266, 0, 0.09860524, 13.383762, 0, -11.9259205)
|
||||||
|
|
||||||
|
[node name="CrowdMarker2" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=409183135]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, -0.0132164955, -23.088398)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker3" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=1443859159]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.533139, -0.0132164955, -24.70021)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker4" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=1737861319]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.021559, -0.013215542, -21.333067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker5" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=1991036767]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.887294, -0.0132164955, -18.725811)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker6" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=1075095199]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.740928, -0.013216019, -28.157803)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker7" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=1699892650]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.6115, -0.013216019, -29.924221)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker8" type="Node3D" parent="Crowd/Back/B_Step4" unique_id=565692575]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.2360125, -0.0132164955, -30.806953)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="B_Step" type="Node3D" parent="Crowd/Back" unique_id=414907102]
|
||||||
|
transform = Transform3D(-0.04323627, 0, 0.99906486, 0, 1, 0, -0.99906486, 0, -0.04323627, -8.380329, -1.827393, -25.818556)
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Back/B_Step" unique_id=1943968269]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Back/B_Step" unique_id=2061244199]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Back/B_Step" unique_id=977400271]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2287855, 0.14058208, 1.9909182)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Back/B_Step" unique_id=1522748404]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Back/B_Step" unique_id=791374389]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Back/B_Step" unique_id=1457299400]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.14058161, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="L_Step5" type="Node3D" parent="Crowd/Back" unique_id=1665844535]
|
||||||
|
transform = Transform3D(0.022304952, 0, 0.99975127, 0, 1, 0, -0.99975127, 0, 0.022304952, -6.9624662, -3.0939345, -25.679592)
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=1629480429]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.3974409, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=1506450411]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.809, 0.39744186, 1.755331)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=622605512]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.3974409, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=257326812]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.3974414, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=1211459457]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.3974414, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker15" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=155390702]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -1.0940075, 0.25685978, -1.355114)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker16" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=34227892]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -3.6482267, 0.3974409, -3.5884771)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker17" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=1904591453]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -0.053198814, 0.39744186, 0.27350044)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker18" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=687360614]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, 1.3811512, 0.3974409, 2.7079024)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker19" type="Node3D" parent="Crowd/Back/L_Step5" unique_id=1751401910]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -6.663862, 0.3974414, -5.0203266)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 1
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="Right" type="Node3D" parent="Crowd" unique_id=1277806052]
|
||||||
|
|
||||||
|
[node name="R_Step3" type="Node3D" parent="Crowd/Right" unique_id=1092390193]
|
||||||
|
transform = Transform3D(-0.9993231, 0, -0.036786973, 0, 1, 0, 0.036786973, 0, -0.9993231, -12.105062, -1.045898, -5.1461663)
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Right/R_Step3" unique_id=550527812]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Right/R_Step3" unique_id=417838082]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Right/R_Step3" unique_id=562183345]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.809, 0.14058208, 1.755331)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Right/R_Step3" unique_id=59142166]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Right/R_Step3" unique_id=1970971214]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Right/R_Step3" unique_id=622146302]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.14058161, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="R_Step4" type="Node3D" parent="Crowd/Right" unique_id=1081871135]
|
||||||
|
transform = Transform3D(-0.99684, 0, 0.07943541, 0, 1, 0, -0.07943541, 0, -0.99684, 2.0997996, 0, -28.147049)
|
||||||
|
|
||||||
|
[node name="CrowdMarker2" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=384688095]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, -0.0132164955, -23.088398)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker3" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=794438773]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.533139, -0.0132164955, -24.70021)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker4" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=318354120]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.021559, -0.013215542, -21.333067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker5" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=1139375927]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.887294, -0.0132164955, -18.725811)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker6" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=468384482]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.740928, -0.013216019, -28.157803)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker7" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=1219838852]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.6115, -0.013216019, -29.924221)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker8" type="Node3D" parent="Crowd/Right/R_Step4" unique_id=876634044]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.2360125, -0.0132164955, -30.806953)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="R_Step1" type="Node3D" parent="Crowd/Right" unique_id=1804391941]
|
||||||
|
transform = Transform3D(-0.9983546, 0, -0.05734154, 0, 1, 0, 0.05734154, 0, -0.9983546, -11.76964, -2.091795, -5.703799)
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Right/R_Step1" unique_id=1122311940]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Right/R_Step1" unique_id=107523420]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.14058113, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Right/R_Step1" unique_id=265148663]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2287855, 0.14058208, 1.9909182)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Right/R_Step1" unique_id=592546407]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.14058113, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Right/R_Step1" unique_id=514985832]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.14058161, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Right/R_Step1" unique_id=416317204]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.14058161, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="L_Step5" type="Node3D" parent="Crowd/Right" unique_id=1434949087]
|
||||||
|
transform = Transform3D(-0.99935067, 0, -0.036034077, 0, 1, 0, 0.036034077, 0, -0.99935067, -10.943644, -3.0939355, -6.015876)
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=1951969393]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.2258072, 0.3974409, -2.5608063)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker11" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=1614578381]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.809, 0.39744186, 1.755331)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker12" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=2079795403]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8979845, 0.3974409, 4.362587)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker13" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=18703002]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0180187, 0.3974414, -4.3905067)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker14" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=619627641]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.9175544, 0.3974414, -5.9128)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker15" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=46013841]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -1.0940075, 0.25685978, -1.355114)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker16" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=1130198232]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -3.6482267, 0.3974409, -3.5884771)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker17" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=744258528]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -0.053198814, 0.39744186, 0.27350044)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker18" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=774368922]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, 1.3811512, 0.3974409, 2.7079024)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker19" type="Node3D" parent="Crowd/Right/L_Step5" unique_id=545323927]
|
||||||
|
transform = Transform3D(0.9906587, 0, 0.13636485, 0, 1, 0, -0.13636485, 0, 0.9906587, -6.663862, 0.3974414, -5.0203266)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 2
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="Front_Right" type="Node3D" parent="Crowd" unique_id=1683981332]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16.440575, 0, -11.451455)
|
||||||
|
|
||||||
|
[node name="CrowdMarker3" type="Node3D" parent="Crowd/Front_Right" unique_id=943299423]
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker4" type="Node3D" parent="Crowd/Front_Right" unique_id=1123847296]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.05489731, 0, 1.3246164)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker5" type="Node3D" parent="Crowd/Front_Right" unique_id=640400607]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7841702, -0.93699646, 0)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker6" type="Node3D" parent="Crowd/Front_Right" unique_id=603945551]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8390675, -0.93699646, 1.3246164)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker7" type="Node3D" parent="Crowd/Front_Right" unique_id=26053676]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.6784592, -2.0288038, 0.21630049)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker8" type="Node3D" parent="Crowd/Front_Right" unique_id=983869769]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.7333565, -2.0288038, 1.5409169)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker9" type="Node3D" parent="Crowd/Front_Right" unique_id=2131633869]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.7693996, -2.7652984, 0)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker10" type="Node3D" parent="Crowd/Front_Right" unique_id=393732717]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.824297, -2.7652984, 1.3246164)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 4
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="Front_Left" type="Node3D" parent="Crowd" unique_id=1426987879]
|
||||||
|
|
||||||
|
[node name="CrowdMarker" type="Node3D" parent="Crowd/Front_Left" unique_id=884656972]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.1676445, 0, 1.5733547)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 3
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker2" type="Node3D" parent="Crowd/Front_Left" unique_id=1281756834]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.1122522, 0, 1.5733547)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 3
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker3" type="Node3D" parent="Crowd/Front_Left" unique_id=287020937]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.483545, -1.8461189, -0.5833006)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 3
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker4" type="Node3D" parent="Crowd/Front_Left" unique_id=1628869900]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.7205377, -1.8461189, -0.36581516)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 3
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker5" type="Node3D" parent="Crowd/Front_Left" unique_id=375235166]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.245149, -0.9629531, 0.27452564)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 3
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[node name="CrowdMarker6" type="Node3D" parent="Crowd/Front_Left" unique_id=1951144178]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.7495923, -0.9629531, 0.48515797)
|
||||||
|
script = ExtResource("9_agtuu")
|
||||||
|
orientation = 3
|
||||||
|
metadata/_custom_type_script = "uid://70j3ks1emhkd"
|
||||||
|
|
||||||
|
[editable path="Node3D/arena_placeholder_v01"]
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
const _GOLD := Color(1.00, 0.78, 0.22)
|
||||||
|
const _CREAM := Color(0.93, 0.88, 0.72)
|
||||||
|
|
||||||
|
const _BAR_W: float = 1260.0
|
||||||
|
const _BAR_H: float = 18.0
|
||||||
|
const _BAR_BG: Color = Color(0.12, 0.04, 0.04, 0.88)
|
||||||
|
const _BAR_FILL: Color = Color(0.65, 0.08, 0.06)
|
||||||
|
const _BAR_ENRAGE: Color = Color(0.90, 0.32, 0.06)
|
||||||
|
|
||||||
|
var _bear: Node = null
|
||||||
|
var _fill: ColorRect = null
|
||||||
|
var _label: Label = null
|
||||||
|
var _max_hp: int = 1
|
||||||
|
var _current_hp: int = 1
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
layer = 10
|
||||||
|
_build_ui()
|
||||||
|
_find_bear.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _find_bear() -> void:
|
||||||
|
var bears := get_tree().get_nodes_in_group(&"bear")
|
||||||
|
if bears.is_empty():
|
||||||
|
return
|
||||||
|
_bear = bears[0]
|
||||||
|
_bear.health_changed.connect(_on_health_changed)
|
||||||
|
_bear.killed.connect(_on_bear_killed)
|
||||||
|
var max_hp := int(DP.f("bear_max_hp"))
|
||||||
|
_on_health_changed(_bear.get_hp(), max_hp)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_health_changed(current: int, max_hp: int) -> void:
|
||||||
|
_current_hp = current
|
||||||
|
_max_hp = max_hp
|
||||||
|
_update_bar()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_bear_killed() -> void:
|
||||||
|
_current_hp = 0
|
||||||
|
_update_bar()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_bar() -> void:
|
||||||
|
if _fill == null:
|
||||||
|
return
|
||||||
|
var frac := 0.0 if _max_hp <= 0 else clampf(float(_current_hp) / float(_max_hp), 0.0, 1.0)
|
||||||
|
_fill.size.x = _BAR_W * frac
|
||||||
|
var enrage_frac: float = DP.f("bear_enrage_frac")
|
||||||
|
_fill.color = _BAR_FILL.lerp(_BAR_ENRAGE, 1.0 - frac / enrage_frac if frac < enrage_frac else 0.0)
|
||||||
|
if _label != null:
|
||||||
|
_label.text = "BEAR" % [maxi(_current_hp, 0), _max_hp]
|
||||||
|
## for hp display in numbers write %d / %d
|
||||||
|
|
||||||
|
|
||||||
|
func _build_ui() -> void:
|
||||||
|
var panel := PanelContainer.new()
|
||||||
|
panel.add_theme_stylebox_override(
|
||||||
|
"panel", UiTheme.plaque(UiTheme.LEATHER, UiTheme.BORDER, 4, 14.0, 8.0))
|
||||||
|
panel.anchor_left = 0.5
|
||||||
|
panel.anchor_right = 0.5
|
||||||
|
panel.anchor_top = 0.0
|
||||||
|
panel.anchor_bottom = 0.0
|
||||||
|
panel.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||||
|
panel.offset_top = 16.0
|
||||||
|
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(panel)
|
||||||
|
|
||||||
|
var col := VBoxContainer.new()
|
||||||
|
col.add_theme_constant_override("separation", 5)
|
||||||
|
col.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
panel.add_child(col)
|
||||||
|
|
||||||
|
_label = Label.new()
|
||||||
|
_label.text = "BEAR"
|
||||||
|
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
_label.add_theme_font_override("font", UiFonts.body())
|
||||||
|
_label.add_theme_color_override("font_color", _GOLD)
|
||||||
|
_label.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.85))
|
||||||
|
_label.add_theme_constant_override("outline_size", 3)
|
||||||
|
_label.add_theme_font_size_override("font_size", 30)
|
||||||
|
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
col.add_child(_label)
|
||||||
|
|
||||||
|
var bar_bg := PanelContainer.new()
|
||||||
|
bar_bg.custom_minimum_size = Vector2(_BAR_W, _BAR_H)
|
||||||
|
bar_bg.clip_contents = true
|
||||||
|
var bg_style := StyleBoxFlat.new()
|
||||||
|
bg_style.bg_color = _BAR_BG
|
||||||
|
bg_style.set_corner_radius_all(4)
|
||||||
|
bg_style.corner_detail = 4
|
||||||
|
bg_style.border_width_bottom = 2
|
||||||
|
bg_style.border_color = Color(UiTheme.GOLD.r, UiTheme.GOLD.g, UiTheme.GOLD.b, 0.35)
|
||||||
|
bar_bg.add_theme_stylebox_override("panel", bg_style)
|
||||||
|
bar_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
col.add_child(bar_bg)
|
||||||
|
|
||||||
|
_fill = ColorRect.new()
|
||||||
|
_fill.color = _BAR_FILL
|
||||||
|
_fill.size = Vector2(_BAR_W, _BAR_H)
|
||||||
|
_fill.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
bar_bg.add_child(_fill)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dfk7dcvki7rqu
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
[gd_scene format=3 uid="uid://dfxigg3cr6nl5"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bm6rmt3xprhjs" path="res://Assets/bear_level_arena.glb" id="1_beararena"]
|
||||||
|
[ext_resource type="Script" uid="uid://bbtlwihtlc1wp" path="res://levels/bear_spawn.gd" id="2_spawn"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://o8ocjfprxa7u" path="res://Bear.tscn" id="3_bear"]
|
||||||
|
[ext_resource type="Script" uid="uid://dfk7dcvki7rqu" path="res://levels/bear_hud.gd" id="4_bearhud"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_floor"]
|
||||||
|
|
||||||
|
[node name="bear_level" type="Node3D" unique_id=261730746]
|
||||||
|
|
||||||
|
[node name="Node3D" type="Node3D" parent="." unique_id=333447532]
|
||||||
|
|
||||||
|
[node name="StaticBody3D" type="StaticBody3D" parent="Node3D" unique_id=1466540248]
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Node3D/StaticBody3D" unique_id=575454169]
|
||||||
|
transform = Transform3D(30, 0, 0, 0, 1, 0, 0, 0, 30, 0, -0.5, 0)
|
||||||
|
shape = SubResource("BoxShape3D_floor")
|
||||||
|
|
||||||
|
[node name="bear_level_arena" parent="." unique_id=1529242945 instance=ExtResource("1_beararena")]
|
||||||
|
transform = Transform3D(1.94, 0, 0, 0, 1.94, 0, 0, 0, 1.94, 0, 0, 0)
|
||||||
|
|
||||||
|
[node name="floor" parent="bear_level_arena/floor" parent_id_path=PackedInt32Array(1529242945, 294762985) index="0" unique_id=1266729531]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="wall" parent="bear_level_arena/floor" parent_id_path=PackedInt32Array(1529242945, 294762985) index="1" unique_id=1445338369]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="BearSpawner" type="Node3D" parent="." unique_id=47931969]
|
||||||
|
script = ExtResource("2_spawn")
|
||||||
|
enemy_scene = ExtResource("3_bear")
|
||||||
|
|
||||||
|
[node name="BearHud" type="CanvasLayer" parent="." unique_id=102938471]
|
||||||
|
script = ExtResource("4_bearhud")
|
||||||
|
|
||||||
|
[node name="Lights" type="Node3D" parent="." unique_id=937122836]
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Lights" unique_id=1786336124]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.7071067, 0.70710677, 0, -0.70710677, 0.7071067, 0, 36.945946, 70)
|
||||||
|
light_color = Color(1, 0.75782466, 0.7332154, 1)
|
||||||
|
light_energy = 1.796
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D2" type="DirectionalLight3D" parent="Lights" unique_id=1308313006]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, -0.8660254, 0.49999994, 0, -0.49999994, -0.8660254, 0, 112.56815, 93.37274)
|
||||||
|
light_color = Color(0, 0.33367383, 0.57971, 1)
|
||||||
|
light_energy = 0.616
|
||||||
|
|
||||||
|
[editable path="bear_level_arena"]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
signal matador_killed
|
||||||
|
signal all_defeated
|
||||||
|
|
||||||
|
@export var enemy_scene: PackedScene = null
|
||||||
|
|
||||||
|
var _alive: int = 0
|
||||||
|
var _resolved: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group(&"matador_spawn")
|
||||||
|
_spawn.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _spawn() -> void:
|
||||||
|
if enemy_scene == null:
|
||||||
|
return
|
||||||
|
var bear: Node3D = enemy_scene.instantiate()
|
||||||
|
bear.position = Vector3(-15,0,0)
|
||||||
|
get_parent().add_child(bear)
|
||||||
|
if bear.has_signal(&"killed"):
|
||||||
|
bear.killed.connect(_on_bear_killed)
|
||||||
|
_alive += 1
|
||||||
|
|
||||||
|
|
||||||
|
func _on_bear_killed() -> void:
|
||||||
|
matador_killed.emit()
|
||||||
|
_alive -= 1
|
||||||
|
if not _resolved:
|
||||||
|
_resolved = true
|
||||||
|
all_defeated.emit()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bbtlwihtlc1wp
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
extends Node3D
|
||||||
|
## The reusable game shell: player, camera, HUD and the PS1 filter — everything that
|
||||||
|
## outlives a single level. On load it instances the active level's module (geometry,
|
||||||
|
## spawners, lighting, colliders) into LevelRoot. Switching levels frees the whole
|
||||||
|
## previous module first, so no old mesh or collider can survive into the next fight.
|
||||||
|
|
||||||
|
@onready var _level_root: Node3D = $LevelRoot
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
Run.ensure_run()
|
||||||
|
load_active_level()
|
||||||
|
|
||||||
|
|
||||||
|
## Tear down the current level module and build the run's active one. Freeing the whole
|
||||||
|
## subtree (not hiding it) is the point: a level's colliders and spawners cease to exist
|
||||||
|
## before the next level's are created.
|
||||||
|
func load_active_level() -> void:
|
||||||
|
for child: Node in _level_root.get_children():
|
||||||
|
_level_root.remove_child(child)
|
||||||
|
child.queue_free()
|
||||||
|
var level := Run.active_level()
|
||||||
|
if level == null or level.level_scene == null:
|
||||||
|
push_warning("game_shell: active level has no level_scene")
|
||||||
|
return
|
||||||
|
_level_root.add_child(level.level_scene.instantiate())
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ptsfs3wq5bje
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
class_name LevelDef
|
||||||
|
extends Resource
|
||||||
|
## Definition of one playable level on the run map — the arena today, later a cave with
|
||||||
|
## a bear, a china shop to smash, and so on. Every map node references a LevelDef, and
|
||||||
|
## the map/flow code never hard-codes a scene: adding a level is a new LevelDef (plus its
|
||||||
|
## scene) handed out by the map generator in run_state.gd.
|
||||||
|
|
||||||
|
@export var id: StringName = &""
|
||||||
|
@export var display_name: String = "Level"
|
||||||
|
## The shell scene loaded when the player enters a node with this level — the reusable
|
||||||
|
## frame (player, camera, HUD, PS1 filter). Shared by every level; the level's own
|
||||||
|
## geometry rides in `level_scene`, which the shell instances at runtime.
|
||||||
|
@export_file("*.tscn") var scene_path: String = ""
|
||||||
|
## The level module instanced into the shell's LevelRoot: spawners, lighting, meshes,
|
||||||
|
## colliders and any level-specific subscripts. Swapped wholesale between levels so no
|
||||||
|
## previous level's geometry (or its colliders) can leak into the next.
|
||||||
|
@export var level_scene: PackedScene = null
|
||||||
|
## Placeholder pip tint on the map until per-level icon art exists.
|
||||||
|
@export var color: Color = Color(0.80, 0.62, 0.24)
|
||||||
|
## A boss level caps a run — its node is drawn larger and clearing it ends the map.
|
||||||
|
@export var is_boss: bool = false
|
||||||
|
## The enemy this level pits the bull against. Null → the usual matador wave (the
|
||||||
|
## spawner's default). Set → a single boss enemy of this scene is spawned instead (e.g.
|
||||||
|
## the bear), and the fight is won when it falls. The arena scene is shared either way;
|
||||||
|
## only the opponent changes.
|
||||||
|
@export var enemy_scene: PackedScene = null
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c6sef0tfl26sr
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[gd_scene format=3 uid="uid://32nyipcwrab5"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cwmo5vh738x6w" path="res://Assets/Lobby_level.glb" id="1_lobby"]
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_lobby"]
|
||||||
|
|
||||||
|
[node name="lobby_level" type="Node3D" unique_id=1511255302]
|
||||||
|
|
||||||
|
[node name="Lobby_level" parent="." unique_id=1119577247 instance=ExtResource("1_lobby")]
|
||||||
|
|
||||||
|
[node name="Lights" type="Node3D" parent="." unique_id=562527333]
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Lights" unique_id=837461706]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.7071067, 0.70710677, 0, -0.70710677, 0.7071067, 0, 36.945946, 70)
|
||||||
|
light_color = Color(1, 0.9137255, 0.827451, 1)
|
||||||
|
light_energy = 1.6
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D2" type="DirectionalLight3D" parent="Lights" unique_id=513226199]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, -0.8660254, 0.49999994, 0, -0.49999994, -0.8660254, 0, 112.56815, 93.37274)
|
||||||
|
light_color = Color(0.4784314, 0.6156863, 0.8, 1)
|
||||||
|
light_energy = 0.45
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=939963056]
|
||||||
|
environment = SubResource("Environment_lobby")
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class_name MapNode
|
||||||
|
extends RefCounted
|
||||||
|
## One node on the run map. Regenerated every run and never persisted, so RefCounted
|
||||||
|
## rather than Resource. `pos` is normalized (0-1) so the map screen can lay the node out
|
||||||
|
## at any resolution; `links` are column indices in the *next* row this node connects to
|
||||||
|
## (the Slay-the-Spire branching path).
|
||||||
|
|
||||||
|
var row: int = 0
|
||||||
|
var col: int = 0
|
||||||
|
var pos: Vector2 = Vector2.ZERO
|
||||||
|
var level: LevelDef = null
|
||||||
|
var links: PackedInt32Array = PackedInt32Array()
|
||||||
|
var cleared: bool = false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://emq8i2bmkekn
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
extends Control
|
||||||
|
## Slay-the-Spire-style run map shown between fights. Placeholder art — coloured pips
|
||||||
|
## joined by paths — but fully interactive: the player picks one of the reachable nodes
|
||||||
|
## and is sent into that level. Every node is the arena for now (see run_state.gd); the
|
||||||
|
## screen is level-agnostic, so richer levels just need a LevelDef with its own colour.
|
||||||
|
|
||||||
|
const _GOLD := Color(1.00, 0.78, 0.22)
|
||||||
|
const _CREAM := Color(0.93, 0.88, 0.72)
|
||||||
|
const _EDGE_DIM := Color(0.45, 0.36, 0.18, 0.55)
|
||||||
|
const _EDGE_ACTIVE := Color(1.00, 0.82, 0.32, 0.95)
|
||||||
|
|
||||||
|
const _NODE_SIZE := 58.0
|
||||||
|
const _BOSS_SIZE := 88.0
|
||||||
|
|
||||||
|
const _MENU_SCENE := "res://MainMenu.tscn"
|
||||||
|
|
||||||
|
var _map_area: Control
|
||||||
|
var _completion: PanelContainer
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
get_tree().paused = false
|
||||||
|
Run.ensure_run()
|
||||||
|
_build_ui()
|
||||||
|
_relayout.call_deferred() # defer so _map_area has its real size first
|
||||||
|
|
||||||
|
|
||||||
|
func _build_ui() -> void:
|
||||||
|
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
|
||||||
|
var bg := ColorRect.new()
|
||||||
|
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
bg.color = Color(0.09, 0.06, 0.03) # opaque leather, no scene showing through
|
||||||
|
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(bg)
|
||||||
|
|
||||||
|
var title := Label.new()
|
||||||
|
title.text = "Choose Your Path"
|
||||||
|
title.set_anchors_preset(Control.PRESET_TOP_WIDE)
|
||||||
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
title.offset_top = 22.0
|
||||||
|
title.add_theme_font_override("font", UiFonts.title())
|
||||||
|
title.add_theme_font_size_override("font_size", 46)
|
||||||
|
title.add_theme_color_override("font_color", _GOLD)
|
||||||
|
title.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
||||||
|
title.add_theme_constant_override("outline_size", 6)
|
||||||
|
title.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(title)
|
||||||
|
|
||||||
|
_map_area = Control.new()
|
||||||
|
_map_area.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
_map_area.offset_top = 108.0
|
||||||
|
_map_area.offset_bottom = -92.0
|
||||||
|
_map_area.offset_left = 48.0
|
||||||
|
_map_area.offset_right = -48.0
|
||||||
|
_map_area.clip_contents = false
|
||||||
|
add_child(_map_area)
|
||||||
|
_map_area.resized.connect(_relayout)
|
||||||
|
|
||||||
|
var menu_btn := _make_button("Main Menu")
|
||||||
|
menu_btn.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||||||
|
menu_btn.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||||
|
menu_btn.offset_left = 32.0
|
||||||
|
menu_btn.offset_bottom = -26.0
|
||||||
|
menu_btn.pressed.connect(_to_menu)
|
||||||
|
add_child(menu_btn)
|
||||||
|
|
||||||
|
_build_completion()
|
||||||
|
|
||||||
|
|
||||||
|
# ── Layout ──────────────────────────────────────────────────────────────────
|
||||||
|
# Rebuilt from the run state on first show and whenever the map area resizes. Edges are
|
||||||
|
# added first so the node pips draw on top of the paths.
|
||||||
|
|
||||||
|
func _relayout() -> void:
|
||||||
|
if _map_area == null:
|
||||||
|
return
|
||||||
|
for child in _map_area.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
var size := _map_area.size
|
||||||
|
if size.x <= 0.0 or size.y <= 0.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var reachable := Run.reachable()
|
||||||
|
var reach_set := {}
|
||||||
|
for n: MapNode in reachable:
|
||||||
|
reach_set[n] = true
|
||||||
|
|
||||||
|
var here := Run.current_node()
|
||||||
|
if Run.player_row < 0:
|
||||||
|
var start_pos := Vector2(size.x * 0.5, size.y - 8.0)
|
||||||
|
_add_start_pip(start_pos)
|
||||||
|
for n: MapNode in reachable:
|
||||||
|
_add_edge(start_pos, _local(n, size), true)
|
||||||
|
for row: Array in Run.map:
|
||||||
|
for from_node: MapNode in row:
|
||||||
|
for to_col: int in from_node.links:
|
||||||
|
var to_node := Run.node_at(from_node.row + 1, to_col)
|
||||||
|
if to_node == null:
|
||||||
|
continue
|
||||||
|
var active := from_node == here and reach_set.has(to_node)
|
||||||
|
_add_edge(_local(from_node, size), _local(to_node, size), active)
|
||||||
|
|
||||||
|
for row: Array in Run.map:
|
||||||
|
for node: MapNode in row:
|
||||||
|
_add_node(node, size, reach_set.has(node))
|
||||||
|
|
||||||
|
_completion.visible = Run.run_complete() or reachable.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
func _local(node: MapNode, size: Vector2) -> Vector2:
|
||||||
|
return Vector2(node.pos.x * size.x, node.pos.y * size.y)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_edge(a: Vector2, b: Vector2, active: bool) -> void:
|
||||||
|
var line := Line2D.new()
|
||||||
|
line.add_point(a)
|
||||||
|
line.add_point(b)
|
||||||
|
line.width = 4.0 if active else 2.5
|
||||||
|
line.default_color = _EDGE_ACTIVE if active else _EDGE_DIM
|
||||||
|
line.antialiased = true
|
||||||
|
_map_area.add_child(line)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_start_pip(center: Vector2) -> void:
|
||||||
|
var pip := PanelContainer.new()
|
||||||
|
var sz := 22.0
|
||||||
|
pip.custom_minimum_size = Vector2(sz, sz)
|
||||||
|
pip.size = Vector2(sz, sz)
|
||||||
|
pip.position = center - Vector2(sz, sz) * 0.5
|
||||||
|
pip.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
pip.add_theme_stylebox_override("panel", UiTheme.disc(_GOLD, _CREAM, 2))
|
||||||
|
_map_area.add_child(pip)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_node(node: MapNode, size: Vector2, is_reachable: bool) -> void:
|
||||||
|
var is_current := node.row == Run.player_row and node.col == Run.player_col
|
||||||
|
var sz := _BOSS_SIZE if node.level.is_boss else _NODE_SIZE
|
||||||
|
var center := _local(node, size)
|
||||||
|
|
||||||
|
var btn := Button.new()
|
||||||
|
btn.custom_minimum_size = Vector2(sz, sz)
|
||||||
|
btn.size = Vector2(sz, sz)
|
||||||
|
btn.position = center - Vector2(sz, sz) * 0.5
|
||||||
|
btn.focus_mode = Control.FOCUS_NONE
|
||||||
|
btn.disabled = not is_reachable
|
||||||
|
btn.mouse_filter = Control.MOUSE_FILTER_STOP if is_reachable else Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
var fill := node.level.color
|
||||||
|
var border := UiTheme.BORDER
|
||||||
|
if is_current:
|
||||||
|
border = _CREAM
|
||||||
|
elif is_reachable:
|
||||||
|
border = _GOLD
|
||||||
|
elif node.cleared:
|
||||||
|
fill = fill.darkened(0.35)
|
||||||
|
border = Color(_GOLD.r, _GOLD.g, _GOLD.b, 0.5)
|
||||||
|
else: # locked — a future row not yet reachable
|
||||||
|
fill = fill.darkened(0.6)
|
||||||
|
border = Color(0.40, 0.34, 0.20, 0.6)
|
||||||
|
|
||||||
|
var border_w := 4 if (is_reachable or is_current) else 2
|
||||||
|
var normal := UiTheme.disc(fill, border, border_w)
|
||||||
|
var hover := UiTheme.disc(fill.lightened(0.18), _CREAM, border_w)
|
||||||
|
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("disabled", normal)
|
||||||
|
_map_area.add_child(btn)
|
||||||
|
|
||||||
|
if is_reachable:
|
||||||
|
btn.pressed.connect(_enter_node.bind(node))
|
||||||
|
_pulse(btn)
|
||||||
|
|
||||||
|
if is_reachable or is_current or node.level.is_boss:
|
||||||
|
_add_caption(node.level.display_name, center + Vector2(0.0, sz * 0.5 + 6.0), is_reachable)
|
||||||
|
if is_current:
|
||||||
|
_add_caption("You are here", center - Vector2(0.0, sz * 0.5 + 18.0), false)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_caption(text: String, center_top: Vector2, bright: bool) -> void:
|
||||||
|
var lbl := Label.new()
|
||||||
|
lbl.text = text
|
||||||
|
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
lbl.size = Vector2(200.0, 0.0)
|
||||||
|
lbl.position = center_top - Vector2(100.0, 0.0)
|
||||||
|
lbl.add_theme_font_override("font", UiFonts.body())
|
||||||
|
lbl.add_theme_font_size_override("font_size", 15)
|
||||||
|
lbl.add_theme_color_override("font_color", _CREAM if bright else UiTheme.MUTED)
|
||||||
|
lbl.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
||||||
|
lbl.add_theme_constant_override("outline_size", 4)
|
||||||
|
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_map_area.add_child(lbl)
|
||||||
|
|
||||||
|
|
||||||
|
# Slow glow on the reachable pips so the eye lands on where the player can go next.
|
||||||
|
func _pulse(node: CanvasItem) -> void:
|
||||||
|
var tw := create_tween().set_loops()
|
||||||
|
tw.tween_property(node, "modulate:a", 0.6, 0.75).set_trans(Tween.TRANS_SINE)
|
||||||
|
tw.tween_property(node, "modulate:a", 1.0, 0.75).set_trans(Tween.TRANS_SINE)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Actions ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _enter_node(node: MapNode) -> void:
|
||||||
|
Run.select_node(node)
|
||||||
|
get_tree().paused = false
|
||||||
|
get_tree().change_scene_to_file(node.level.scene_path)
|
||||||
|
|
||||||
|
|
||||||
|
func _to_menu() -> void:
|
||||||
|
get_tree().paused = false
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
get_tree().change_scene_to_file(_MENU_SCENE)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Run-complete overlay ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _build_completion() -> void:
|
||||||
|
_completion = PanelContainer.new()
|
||||||
|
_completion.set_anchors_preset(Control.PRESET_CENTER)
|
||||||
|
_completion.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||||
|
_completion.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||||
|
_completion.add_theme_stylebox_override(
|
||||||
|
"panel", UiTheme.plaque(UiTheme.LEATHER, _GOLD, 4, 32.0, 24.0)
|
||||||
|
)
|
||||||
|
_completion.visible = false
|
||||||
|
add_child(_completion)
|
||||||
|
|
||||||
|
var col := VBoxContainer.new()
|
||||||
|
col.add_theme_constant_override("separation", 16)
|
||||||
|
col.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
_completion.add_child(col)
|
||||||
|
|
||||||
|
var heading := Label.new()
|
||||||
|
heading.text = "Run Complete!"
|
||||||
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
heading.add_theme_font_override("font", UiFonts.title())
|
||||||
|
heading.add_theme_font_size_override("font_size", 40)
|
||||||
|
heading.add_theme_color_override("font_color", _GOLD)
|
||||||
|
heading.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
||||||
|
heading.add_theme_constant_override("outline_size", 5)
|
||||||
|
col.add_child(heading)
|
||||||
|
|
||||||
|
var row := HBoxContainer.new()
|
||||||
|
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
row.add_theme_constant_override("separation", 16)
|
||||||
|
col.add_child(row)
|
||||||
|
|
||||||
|
var again := _make_button("New Run")
|
||||||
|
again.pressed.connect(_new_run)
|
||||||
|
row.add_child(again)
|
||||||
|
|
||||||
|
var menu := _make_button("Main Menu")
|
||||||
|
menu.pressed.connect(_to_menu)
|
||||||
|
row.add_child(menu)
|
||||||
|
|
||||||
|
|
||||||
|
func _new_run() -> void:
|
||||||
|
Run.start_new_run()
|
||||||
|
_relayout()
|
||||||
|
|
||||||
|
|
||||||
|
# Shared thematic button, matching the HUD / menu plaques.
|
||||||
|
func _make_button(text: String) -> Button:
|
||||||
|
var btn := Button.new()
|
||||||
|
btn.text = text
|
||||||
|
btn.focus_mode = Control.FOCUS_NONE
|
||||||
|
btn.add_theme_stylebox_override(
|
||||||
|
"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("hover", hover)
|
||||||
|
btn.add_theme_stylebox_override("pressed", 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", _CREAM)
|
||||||
|
btn.add_theme_font_size_override("font_size", 18)
|
||||||
|
return btn
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dt3ffakbwlkmc
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
signal matador_killed
|
||||||
|
signal all_defeated
|
||||||
|
|
||||||
|
const MATADOR := preload("res://Matador.tscn")
|
||||||
|
|
||||||
|
var _alive: int = 0
|
||||||
|
var _wave: int = 0
|
||||||
|
var _resolved: bool = false
|
||||||
|
var _spawn_points: Array[Node3D] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group(&"matador_spawn")
|
||||||
|
_spawn.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
# Each spawner node is a spawn-point marker. The first in tree order (the one the HUD
|
||||||
|
# connects its all_defeated to) runs the wave logic and uses every marker — itself and
|
||||||
|
# its siblings — as an entry point; the rest stay passive so waves aren't run four times.
|
||||||
|
func _spawn() -> void:
|
||||||
|
var markers := get_tree().get_nodes_in_group(&"matador_spawn")
|
||||||
|
if markers.is_empty() or markers[0] != self:
|
||||||
|
return
|
||||||
|
for node: Node in markers:
|
||||||
|
if node is Node3D:
|
||||||
|
_spawn_points.append(node as Node3D)
|
||||||
|
_place_bull(randf() * TAU, DP.f("arena_spawn_radius"))
|
||||||
|
_start_next_wave()
|
||||||
|
|
||||||
|
|
||||||
|
# Wave 1 sends 1 matador, wave 2 sends 2, … up to the number of placed spawn points.
|
||||||
|
# Matadors are assigned to a random subset of spawn points each wave and enter staggered.
|
||||||
|
func _start_next_wave() -> void:
|
||||||
|
_wave += 1
|
||||||
|
|
||||||
|
# 8 waves total.
|
||||||
|
if _wave > DP.f("mat_wave_size"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Wave 1 = 1 matador, Wave 2 = 2, ... Wave 8 = 8.
|
||||||
|
var count := _wave
|
||||||
|
|
||||||
|
# Shuffle the spawn points so the spawn locations are random.
|
||||||
|
var points: Array[Node3D] = _spawn_points.duplicate()
|
||||||
|
points.shuffle()
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
# Reuse spawn points when we have more matadors than points.
|
||||||
|
var sp: Node3D = points[i % points.size()]
|
||||||
|
|
||||||
|
var m := _spawn_at(sp.global_position)
|
||||||
|
m.call_deferred(&"enter_spawn_roll", Vector3.ZERO)
|
||||||
|
|
||||||
|
if i < count - 1:
|
||||||
|
await get_tree().create_timer(
|
||||||
|
DP.f("mat_spawn_stagger")
|
||||||
|
).timeout
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _spawn_at(pos: Vector3) -> Node3D:
|
||||||
|
var m: Node3D = MATADOR.instantiate()
|
||||||
|
m.position = pos
|
||||||
|
get_parent().add_child(m)
|
||||||
|
m.killed.connect(_on_matador_killed)
|
||||||
|
_alive += 1
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
func _on_matador_killed() -> void:
|
||||||
|
matador_killed.emit()
|
||||||
|
_alive -= 1
|
||||||
|
if _alive > 0 or _resolved:
|
||||||
|
return
|
||||||
|
if _wave >= DP.f("mat_wave_size"):
|
||||||
|
_resolved = true
|
||||||
|
all_defeated.emit()
|
||||||
|
else:
|
||||||
|
await get_tree().create_timer(DP.f("mat_wave_pause")).timeout
|
||||||
|
_start_next_wave()
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
extends Node
|
||||||
|
## Persistent run progression across scene changes — the Slay-the-Spire-style map and
|
||||||
|
## where the player sits on it. The arena scene is reloaded for every fight, so this
|
||||||
|
## state must outlive scene changes: genuinely global run state earns an autoload (`Run`).
|
||||||
|
##
|
||||||
|
## Flow: menu Play → start_new_run() → arena (intro fight) → win → MapScreen shows the
|
||||||
|
## first frontier → pick a node → its level scene → win → back to MapScreen advanced one
|
||||||
|
## row, and on until a boss node ends the map. Every node is the arena for now; adding a
|
||||||
|
## level later is a new LevelDef in the catalog and pointing the generator at it.
|
||||||
|
|
||||||
|
signal run_started
|
||||||
|
|
||||||
|
# Every level loads the same reusable shell (player, camera, HUD, PS1 filter); the shell
|
||||||
|
# then instances the level's own `level_scene` module into its LevelRoot.
|
||||||
|
const _SHELL_SCENE := "res://scene.tscn"
|
||||||
|
const _ARENA_LEVEL := preload("res://levels/arena_level.tscn")
|
||||||
|
const _BEAR_LEVEL := preload("res://levels/bear_level.tscn")
|
||||||
|
const _LOBBY_LEVEL := preload("res://levels/lobby_level.tscn")
|
||||||
|
|
||||||
|
# Map shape. Small + placeholder for now; the final row is a single boss node.
|
||||||
|
const _ROWS := 5
|
||||||
|
const _MIN_PER_ROW := 2
|
||||||
|
const _MAX_PER_ROW := 4
|
||||||
|
|
||||||
|
## map[row] is an Array[MapNode]; empty until a run starts.
|
||||||
|
var map: Array = []
|
||||||
|
## Node the player currently occupies. (-1, -1) = the start gate below the first row —
|
||||||
|
## i.e. straight after the intro fight, before any map node has been chosen.
|
||||||
|
var player_row: int = -1
|
||||||
|
var player_col: int = -1
|
||||||
|
## Node picked on the map whose level is being played now; committed to on the next win.
|
||||||
|
var pending_row: int = -1
|
||||||
|
var pending_col: int = -1
|
||||||
|
## Debug override — when set, active_level() returns this regardless of map state.
|
||||||
|
## Set via the console `level` command; cleared on start_new_run().
|
||||||
|
var debug_level_override: LevelDef = null
|
||||||
|
## The lobby is an interstitial hub between fights, not a map node: the player is sent
|
||||||
|
## here after clearing a level (go_to_lobby()) to move around freely before choosing the
|
||||||
|
## next node. While set, active_level() serves the lobby; cleared once a node is picked
|
||||||
|
## (select_node) or a fresh run begins.
|
||||||
|
var enter_lobby: bool = false
|
||||||
|
|
||||||
|
# Level catalog. Every level shares the shell scene; what changes is the level module
|
||||||
|
# (its geometry + spawners) and the opponent. The arena (matador wave, normal + boss
|
||||||
|
# flavours) and the bear (a lone boss enemy that can turn up as a normal node) live here.
|
||||||
|
# Future levels — china shop — slot in the same way: a new module + a LevelDef entry.
|
||||||
|
const _BEAR_SCENE := preload("res://Bear.tscn")
|
||||||
|
|
||||||
|
var _arena: LevelDef
|
||||||
|
var _arena_boss: LevelDef
|
||||||
|
var _bear: LevelDef
|
||||||
|
var _lobby: LevelDef
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_arena = _make_level(&"arena", "The Arena", _ARENA_LEVEL, Color(0.80, 0.62, 0.24), false)
|
||||||
|
_arena_boss = _make_level(
|
||||||
|
&"arena_boss", "Grand Arena", _ARENA_LEVEL, Color(0.88, 0.30, 0.20), true
|
||||||
|
)
|
||||||
|
_bear = _make_level(&"bear", "Bear's Den", _BEAR_LEVEL, Color(0.55, 0.38, 0.22), false)
|
||||||
|
_bear.enemy_scene = _BEAR_SCENE
|
||||||
|
# The lobby is a fightless hub — no enemy_scene, so no wave spawns and no win/lose.
|
||||||
|
_lobby = _make_level(&"lobby", "Lobby", _LOBBY_LEVEL, Color(0.62, 0.66, 0.72), false)
|
||||||
|
|
||||||
|
|
||||||
|
func _make_level(
|
||||||
|
id: StringName, level_name: String, level_scene: PackedScene, color: Color, boss: bool
|
||||||
|
) -> LevelDef:
|
||||||
|
var l := LevelDef.new()
|
||||||
|
l.id = id
|
||||||
|
l.display_name = level_name
|
||||||
|
l.scene_path = _SHELL_SCENE
|
||||||
|
l.level_scene = level_scene
|
||||||
|
l.color = color
|
||||||
|
l.is_boss = boss
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
## The level being fought right now: debug override wins, then the pending/current map
|
||||||
|
## node, falling back to the plain arena for the intro fight or a direct scene launch.
|
||||||
|
func active_level() -> LevelDef:
|
||||||
|
if debug_level_override != null:
|
||||||
|
return debug_level_override
|
||||||
|
if enter_lobby:
|
||||||
|
return _lobby
|
||||||
|
var n := node_at(pending_row, pending_col)
|
||||||
|
if n == null:
|
||||||
|
n = current_node()
|
||||||
|
return n.level if n != null and n.level != null else _arena
|
||||||
|
|
||||||
|
|
||||||
|
## Return a level by id, or null if unknown. Used by the debug console `level` command.
|
||||||
|
func get_level(id: StringName) -> LevelDef:
|
||||||
|
match id:
|
||||||
|
&"arena": return _arena
|
||||||
|
&"arena_boss": return _arena_boss
|
||||||
|
&"bear": return _bear
|
||||||
|
&"lobby": return _lobby
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
## All known level ids — shown by `level` in the console.
|
||||||
|
func level_ids() -> Array[StringName]:
|
||||||
|
return [&"arena", &"arena_boss", &"bear", &"lobby"]
|
||||||
|
|
||||||
|
|
||||||
|
## Begin a fresh run: build a new map and place the player at the start gate. Called from
|
||||||
|
## the main-menu Play button, not from the arena scene (which reloads for every fight).
|
||||||
|
func start_new_run() -> void:
|
||||||
|
_generate_map()
|
||||||
|
player_row = -1
|
||||||
|
player_col = -1
|
||||||
|
pending_row = -1
|
||||||
|
pending_col = -1
|
||||||
|
debug_level_override = null
|
||||||
|
enter_lobby = false
|
||||||
|
run_started.emit()
|
||||||
|
|
||||||
|
|
||||||
|
## Build a run if none exists — a safety net for entering the map or arena directly (e.g.
|
||||||
|
## running scene.tscn straight from the editor) without coming through the menu.
|
||||||
|
func ensure_run() -> void:
|
||||||
|
if map.is_empty():
|
||||||
|
start_new_run()
|
||||||
|
|
||||||
|
|
||||||
|
func has_run() -> bool:
|
||||||
|
return not map.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
func node_at(row: int, col: int) -> MapNode:
|
||||||
|
if row < 0 or row >= map.size():
|
||||||
|
return null
|
||||||
|
var r: Array = map[row]
|
||||||
|
if col < 0 or col >= r.size():
|
||||||
|
return null
|
||||||
|
return r[col]
|
||||||
|
|
||||||
|
|
||||||
|
func current_node() -> MapNode:
|
||||||
|
return node_at(player_row, player_col)
|
||||||
|
|
||||||
|
|
||||||
|
## Nodes the player may pick next: the whole first row from the start gate, otherwise the
|
||||||
|
## nodes the current node links forward to.
|
||||||
|
func reachable() -> Array:
|
||||||
|
if map.is_empty():
|
||||||
|
return []
|
||||||
|
if player_row < 0:
|
||||||
|
return (map[0] as Array).duplicate()
|
||||||
|
var here := current_node()
|
||||||
|
if here == null or player_row + 1 >= map.size():
|
||||||
|
return []
|
||||||
|
var out: Array = []
|
||||||
|
for c: int in here.links:
|
||||||
|
var n := node_at(player_row + 1, c)
|
||||||
|
if n != null:
|
||||||
|
out.append(n)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
## True once the player has cleared a boss node — the map is finished.
|
||||||
|
func run_complete() -> bool:
|
||||||
|
var here := current_node()
|
||||||
|
return here != null and here.level != null and here.level.is_boss
|
||||||
|
|
||||||
|
|
||||||
|
## Mark a node as the one being played (its level scene is about to load).
|
||||||
|
func select_node(node: MapNode) -> void:
|
||||||
|
if node == null:
|
||||||
|
return
|
||||||
|
enter_lobby = false
|
||||||
|
pending_row = node.row
|
||||||
|
pending_col = node.col
|
||||||
|
|
||||||
|
|
||||||
|
## Send the player to the lobby hub instead of a fight — active_level() serves the lobby
|
||||||
|
## until a node is picked. Called after clearing a level so the player lands in the lobby
|
||||||
|
## rather than jumping straight to the next fight.
|
||||||
|
func go_to_lobby() -> void:
|
||||||
|
enter_lobby = true
|
||||||
|
|
||||||
|
|
||||||
|
## Commit the level just won: move the player onto the pending node (or leave them at the
|
||||||
|
## start gate after the intro fight, which has no pending node).
|
||||||
|
func complete_current_level() -> void:
|
||||||
|
if pending_row >= 0:
|
||||||
|
player_row = pending_row
|
||||||
|
player_col = pending_col
|
||||||
|
var n := current_node()
|
||||||
|
if n != null:
|
||||||
|
n.cleared = true
|
||||||
|
pending_row = -1
|
||||||
|
pending_col = -1
|
||||||
|
|
||||||
|
|
||||||
|
# ── Map generation ────────────────────────────────────────────────────────────
|
||||||
|
# A small branching lattice: each row gets a handful of nodes, each node links to the
|
||||||
|
# nearest node(s) in the next row, and every next-row node is guaranteed a parent so the
|
||||||
|
# path is always connected (no orphan the player can never reach). The final row is a
|
||||||
|
# single boss node the whole map funnels toward.
|
||||||
|
|
||||||
|
func _generate_map() -> void:
|
||||||
|
map.clear()
|
||||||
|
for r: int in _ROWS:
|
||||||
|
var count := 1 if r == _ROWS - 1 else randi_range(_MIN_PER_ROW, _MAX_PER_ROW)
|
||||||
|
var y := 1.0 - float(r + 1) / float(_ROWS + 1) # row 0 near the bottom, boss on top
|
||||||
|
var row_nodes: Array = []
|
||||||
|
for c: int in count:
|
||||||
|
var node := MapNode.new()
|
||||||
|
node.row = r
|
||||||
|
node.col = c
|
||||||
|
node.pos = Vector2(_column_x(c, count), y)
|
||||||
|
# Boss row is always the Grand Arena; normal nodes are a mix of matador
|
||||||
|
# arenas and the occasional Bear's Den for variety.
|
||||||
|
if r == _ROWS - 1:
|
||||||
|
node.level = _arena_boss
|
||||||
|
else:
|
||||||
|
node.level = _bear if randf() < 0.35 else _arena
|
||||||
|
row_nodes.append(node)
|
||||||
|
map.append(row_nodes)
|
||||||
|
_link_rows()
|
||||||
|
|
||||||
|
|
||||||
|
func _column_x(col: int, count: int) -> float:
|
||||||
|
if count <= 1:
|
||||||
|
return 0.5
|
||||||
|
return lerpf(0.18, 0.82, float(col) / float(count - 1))
|
||||||
|
|
||||||
|
|
||||||
|
func _link_rows() -> void:
|
||||||
|
for r: int in _ROWS - 1:
|
||||||
|
var cur: Array = map[r]
|
||||||
|
var nxt: Array = map[r + 1]
|
||||||
|
var incoming := PackedInt32Array()
|
||||||
|
incoming.resize(nxt.size()) # zero-filled
|
||||||
|
for from_node: MapNode in cur:
|
||||||
|
var nearest := _nearest_index(from_node, nxt)
|
||||||
|
_add_link(from_node, nearest, incoming)
|
||||||
|
# Occasionally fan out to an adjacent next-row node so paths branch.
|
||||||
|
if nxt.size() > 1 and randf() < 0.45:
|
||||||
|
var step := 1 if randf() < 0.5 else -1
|
||||||
|
_add_link(from_node, clampi(nearest + step, 0, nxt.size() - 1), incoming)
|
||||||
|
# Guarantee every next-row node has at least one parent (no orphans).
|
||||||
|
for c: int in nxt.size():
|
||||||
|
if incoming[c] == 0:
|
||||||
|
_add_link(cur[_nearest_index(nxt[c], cur)], c, incoming)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_link(from_node: MapNode, to_col: int, incoming: PackedInt32Array) -> void:
|
||||||
|
if to_col in from_node.links:
|
||||||
|
return
|
||||||
|
from_node.links.append(to_col)
|
||||||
|
incoming[to_col] += 1
|
||||||
|
|
||||||
|
|
||||||
|
# Index of the node in `others` whose column position is closest to `node` on the x axis —
|
||||||
|
# used both to link a node forward and to adopt an orphan back to its nearest parent.
|
||||||
|
func _nearest_index(node: MapNode, others: Array) -> int:
|
||||||
|
var best := 0
|
||||||
|
var best_d := INF
|
||||||
|
for i: int in others.size():
|
||||||
|
var d: float = absf((others[i] as MapNode).pos.x - node.pos.x)
|
||||||
|
if d < best_d:
|
||||||
|
best_d = d
|
||||||
|
best = i
|
||||||
|
return best
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://u4fartm52bk3
|
||||||
@@ -190,13 +190,74 @@ func _process(_delta: float) -> void:
|
|||||||
play_button.anchor_bottom = cape.y
|
play_button.anchor_bottom = cape.y
|
||||||
|
|
||||||
|
|
||||||
# Player-facing option toggles, seated above the rebind list in the Options panel:
|
# Player-facing option toggles and audio sliders, seated above the rebind list.
|
||||||
# the master screen-shake switch and the gore toggle (both persisted via Settings).
|
|
||||||
func _build_options_toggles() -> void:
|
func _build_options_toggles() -> void:
|
||||||
var scroll_idx := options_scroll.get_index()
|
var idx := options_scroll.get_index()
|
||||||
# Insert in reverse so both land directly above the rebind scroll, in listed order.
|
_add_section_label("── Audio ──", idx); idx += 1
|
||||||
_add_toggle_row("Blood & Gore", Settings.gore, Settings.set_gore, scroll_idx)
|
_add_slider_row("Master Volume", Settings.volume_master, Settings.set_volume_master, idx); idx += 1
|
||||||
_add_toggle_row("Screen Shake", Settings.screen_shake, Settings.set_screen_shake, scroll_idx)
|
_add_slider_row("Ambient Volume", Settings.volume_ambient, Settings.set_volume_ambient, idx); idx += 1
|
||||||
|
_add_slider_row("Effects Volume", Settings.volume_effects, Settings.set_volume_effects, idx); idx += 1
|
||||||
|
_add_toggle_row("Mute All", Settings.mute_all, Settings.set_mute_all, idx); idx += 1
|
||||||
|
_add_section_label("── Gameplay ──", idx); idx += 1
|
||||||
|
_add_toggle_row("Screen Shake", Settings.screen_shake, Settings.set_screen_shake, idx); idx += 1
|
||||||
|
_add_toggle_row("Blood & Gore", Settings.gore, Settings.set_gore, idx); idx += 1
|
||||||
|
_add_section_label("── Controls ──", idx)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_section_label(text: String, at_index: int) -> void:
|
||||||
|
var lbl := Label.new()
|
||||||
|
lbl.text = text
|
||||||
|
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
lbl.add_theme_font_override("font", UiFonts.title())
|
||||||
|
lbl.add_theme_font_size_override("font_size", 18)
|
||||||
|
lbl.add_theme_color_override("font_color", MENU_GOLD)
|
||||||
|
lbl.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||||
|
lbl.add_theme_constant_override("outline_size", 4)
|
||||||
|
options_vbox.add_child(lbl)
|
||||||
|
options_vbox.move_child(lbl, at_index)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_slider_row(label_text: String, value: float, setter: Callable, at_index: int) -> void:
|
||||||
|
var row := HBoxContainer.new()
|
||||||
|
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
var name_label := Label.new()
|
||||||
|
name_label.text = label_text
|
||||||
|
name_label.custom_minimum_size = Vector2(160, 0)
|
||||||
|
name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
name_label.add_theme_font_override("font", UiFonts.body())
|
||||||
|
name_label.add_theme_font_size_override("font_size", 20)
|
||||||
|
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", 5)
|
||||||
|
row.add_child(name_label)
|
||||||
|
|
||||||
|
var slider := HSlider.new()
|
||||||
|
slider.min_value = 0.0
|
||||||
|
slider.max_value = 1.0
|
||||||
|
slider.step = 0.01
|
||||||
|
slider.value = value
|
||||||
|
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
slider.custom_minimum_size = Vector2(0, 28)
|
||||||
|
row.add_child(slider)
|
||||||
|
|
||||||
|
var pct_label := Label.new()
|
||||||
|
pct_label.text = "%d%%" % roundi(value * 100.0)
|
||||||
|
pct_label.custom_minimum_size = Vector2(46, 0)
|
||||||
|
pct_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
pct_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
pct_label.add_theme_font_override("font", UiFonts.body())
|
||||||
|
pct_label.add_theme_font_size_override("font_size", 18)
|
||||||
|
pct_label.add_theme_color_override("font_color", MENU_GOLD)
|
||||||
|
row.add_child(pct_label)
|
||||||
|
|
||||||
|
slider.value_changed.connect(func(v: float) -> void:
|
||||||
|
setter.call(v)
|
||||||
|
pct_label.text = "%d%%" % roundi(v * 100.0))
|
||||||
|
|
||||||
|
options_vbox.add_child(row)
|
||||||
|
options_vbox.move_child(row, at_index)
|
||||||
|
|
||||||
|
|
||||||
func _add_toggle_row(label_text: String, value: bool, setter: Callable, at_index: int) -> void:
|
func _add_toggle_row(label_text: String, value: bool, setter: Callable, at_index: int) -> void:
|
||||||
@@ -254,6 +315,7 @@ func _build_rebind_rows() -> void:
|
|||||||
|
|
||||||
func _on_play_pressed() -> void:
|
func _on_play_pressed() -> void:
|
||||||
_set_buttons_disabled(true)
|
_set_buttons_disabled(true)
|
||||||
|
Run.start_new_run() # fresh map for this run; the arena is the intro fight
|
||||||
var tween := create_tween()
|
var tween := create_tween()
|
||||||
tween.tween_property(fade_rect, "color:a", 1.0, FADE_TIME)
|
tween.tween_property(fade_rect, "color:a", 1.0, FADE_TIME)
|
||||||
tween.tween_callback(func() -> void:
|
tween.tween_callback(func() -> void:
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ var _swing_timer: float = 0.0
|
|||||||
var _lunge_timer: float = 0.0
|
var _lunge_timer: float = 0.0
|
||||||
var _roll_timer: float = 0.0
|
var _roll_timer: float = 0.0
|
||||||
var _roll_dir: Vector3 = Vector3.ZERO
|
var _roll_dir: Vector3 = Vector3.ZERO
|
||||||
|
var _is_spawning: bool = false
|
||||||
var _throw_timer: float = 0.0
|
var _throw_timer: float = 0.0
|
||||||
var _throw_aim_cd: float = 0.0
|
var _throw_aim_cd: float = 0.0
|
||||||
var _throw_dir: Vector3 = Vector3.ZERO
|
var _throw_dir: Vector3 = Vector3.ZERO
|
||||||
@@ -60,6 +61,7 @@ var _blood_burst: CPUParticles3D = null
|
|||||||
@onready var _mesh: Node3D = $matador2
|
@onready var _mesh: Node3D = $matador2
|
||||||
@onready var _hit_area: Area3D = $HitArea
|
@onready var _hit_area: Area3D = $HitArea
|
||||||
|
|
||||||
|
|
||||||
const _ANIM_RUN: StringName = &"Run"
|
const _ANIM_RUN: StringName = &"Run"
|
||||||
const _ANIM_IDLE: StringName = &"Taunt"
|
const _ANIM_IDLE: StringName = &"Taunt"
|
||||||
const _ANIM_ATTACK: StringName = &"Attack"
|
const _ANIM_ATTACK: StringName = &"Attack"
|
||||||
@@ -92,12 +94,19 @@ const _TURN_FACE: float = 16.0 # face the bull while engaging
|
|||||||
const _TURN_SHARP: float = 24.0 # snap onto a sidestep / roll direction
|
const _TURN_SHARP: float = 24.0 # snap onto a sidestep / roll direction
|
||||||
|
|
||||||
|
|
||||||
|
const RigidSkin = preload("res://rigid_skin.gd")
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
add_to_group(&"matador")
|
add_to_group(&"matador")
|
||||||
_skeleton = _find_skeleton(_mesh)
|
_skeleton = _find_skeleton(_mesh)
|
||||||
_anim_player = _find_anim_player(_mesh)
|
_anim_player = _find_anim_player(_mesh)
|
||||||
if _skeleton:
|
if _skeleton:
|
||||||
_sim = MatadorRagdoll.build(_skeleton)
|
_sim = MatadorRagdoll.build(_skeleton)
|
||||||
|
# Web (Mali/ANGLE) can't run Compatibility vertex-skinning; rebuild as non-skinned
|
||||||
|
# bone-attached pieces. Runs after the ragdoll sim is built (both drive the same bones).
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
RigidSkin.convert_tree(self)
|
||||||
if _anim_player:
|
if _anim_player:
|
||||||
for anim in [_ANIM_RUN, _ANIM_ATTACK] + _ANIM_TAUNTS:
|
for anim in [_ANIM_RUN, _ANIM_ATTACK] + _ANIM_TAUNTS:
|
||||||
_ensure_loop(anim)
|
_ensure_loop(anim)
|
||||||
@@ -118,11 +127,13 @@ func _ready() -> void:
|
|||||||
_ole_player = AudioStreamPlayer.new()
|
_ole_player = AudioStreamPlayer.new()
|
||||||
_ole_player.stream = load("res://sounds/ole.ogg")
|
_ole_player.stream = load("res://sounds/ole.ogg")
|
||||||
_ole_player.volume_db = 0.0
|
_ole_player.volume_db = 0.0
|
||||||
|
_ole_player.bus = &"Effects"
|
||||||
add_child(_ole_player)
|
add_child(_ole_player)
|
||||||
if ResourceLoader.exists("res://sounds/matador_death.ogg"):
|
if ResourceLoader.exists("res://sounds/matador_death.ogg"):
|
||||||
_death_player = AudioStreamPlayer.new()
|
_death_player = AudioStreamPlayer.new()
|
||||||
_death_player.stream = load("res://sounds/matador_death.ogg")
|
_death_player.stream = load("res://sounds/matador_death.ogg")
|
||||||
_death_player.volume_db = 2.0
|
_death_player.volume_db = 2.0
|
||||||
|
_death_player.bus = &"Effects"
|
||||||
add_child(_death_player)
|
add_child(_death_player)
|
||||||
|
|
||||||
|
|
||||||
@@ -143,6 +154,10 @@ func _acquire_bull() -> void:
|
|||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
_acquire_bull()
|
_acquire_bull()
|
||||||
|
|
||||||
|
if _is_spawning and is_on_floor():
|
||||||
|
_is_spawning = false
|
||||||
|
collision_layer = 1
|
||||||
|
|
||||||
if DP.b("show_raycasts") and _steer_dir != Vector3.ZERO:
|
if DP.b("show_raycasts") and _steer_dir != Vector3.ZERO:
|
||||||
var o := global_position + Vector3.UP * 0.6
|
var o := global_position + Vector3.UP * 0.6
|
||||||
DebugDraw.ray(o, o + _steer_dir * _STEER_LOOKAHEAD, Color(0.3, 1.0, 0.9))
|
DebugDraw.ray(o, o + _steer_dir * _STEER_LOOKAHEAD, Color(0.3, 1.0, 0.9))
|
||||||
@@ -537,7 +552,7 @@ func _tick_attack(delta: float) -> void:
|
|||||||
|
|
||||||
if _swing_timer > 0.0:
|
if _swing_timer > 0.0:
|
||||||
_lunge_timer = maxf(0.0, _lunge_timer - delta)
|
_lunge_timer = maxf(0.0, _lunge_timer - delta)
|
||||||
if _lunge_timer > 0.0 and dist > 0.1:
|
if _lunge_timer > 0.0 and dist > DP.f("mat_attack_min_dist"):
|
||||||
_accelerate(to_bull.normalized(), DP.f("mat_lunge_speed"), delta)
|
_accelerate(to_bull.normalized(), DP.f("mat_lunge_speed"), delta)
|
||||||
else:
|
else:
|
||||||
_decelerate(22.0, delta)
|
_decelerate(22.0, delta)
|
||||||
@@ -564,6 +579,23 @@ func _enter_roll(hit_dir: Vector3) -> void:
|
|||||||
_anim_player.seek(0.0, true)
|
_anim_player.seek(0.0, true)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by the spawner (deferred) to make the matador leap-roll toward a target
|
||||||
|
# point on entry — the cinematic arena entrance. After the roll it transitions to
|
||||||
|
# normal AI via the standard ROLL → FLEE path.
|
||||||
|
func enter_spawn_roll(toward: Vector3) -> void:
|
||||||
|
var dir := toward - global_position
|
||||||
|
dir.y = 0.0
|
||||||
|
_roll_dir = dir.normalized() if dir.length() > 0.1 else -_mesh.global_transform.basis.z
|
||||||
|
_state = State.ROLL
|
||||||
|
_roll_timer = _anim_length(_ANIM_ROLL, DP.f("mat_roll_duration"))
|
||||||
|
_is_spawning = true
|
||||||
|
collision_layer = 0
|
||||||
|
velocity.y = 4.0
|
||||||
|
if _anim_player and _anim_player.has_animation(_ANIM_ROLL):
|
||||||
|
_anim_player.play(_ANIM_ROLL)
|
||||||
|
_anim_player.seek(0.0, true)
|
||||||
|
|
||||||
|
|
||||||
func _tick_roll(delta: float) -> void:
|
func _tick_roll(delta: float) -> void:
|
||||||
_roll_timer -= delta
|
_roll_timer -= delta
|
||||||
if _roll_timer <= 0.0:
|
if _roll_timer <= 0.0:
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
|
|
||||||
signal matador_killed
|
|
||||||
signal all_defeated
|
|
||||||
|
|
||||||
const MATADOR := preload("res://Matador.tscn")
|
|
||||||
|
|
||||||
var _spawn_count: int = 1
|
|
||||||
var _alive: int = 0
|
|
||||||
var _kills: int = 0
|
|
||||||
var _resolved: bool = false
|
|
||||||
|
|
||||||
# Flat (x,z) positions of the most recent batch spawned. A fresh wave keeps at least
|
|
||||||
# _SPAWN_MIN_SEP away from these so it never lands on top of where the last one did.
|
|
||||||
const _SPAWN_MIN_SEP: float = 3.5
|
|
||||||
var _last_spawn_positions: Array[Vector3] = []
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
add_to_group(&"matador_spawn")
|
|
||||||
_spawn_count = maxi(1, int(DP.f("mat_spawn_count")))
|
|
||||||
# Deferred so it's safe from _ready and matches the previous spawn timing.
|
|
||||||
_spawn.call_deferred()
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn() -> void:
|
|
||||||
# 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)
|
|
||||||
var placed: Array[Vector3] = _spread_positions(_spawn_count, bull_angle + PI, arena_r)
|
|
||||||
for pos: Vector3 in placed:
|
|
||||||
_spawn_at(pos)
|
|
||||||
_last_spawn_positions = placed
|
|
||||||
|
|
||||||
|
|
||||||
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, spread across the far side (centred on `center_angle`)
|
|
||||||
# so every matador stays near the point diametrically opposite the bull. Each slot gets
|
|
||||||
# its own angular band and a staggered radius, then rejection-samples inside that band so
|
|
||||||
# no two matadors land closer than _SPAWN_MIN_SEP — otherwise they pile up on top of
|
|
||||||
# each other on spawn. The arc widens with the crowd so a big wave doesn't get cramped.
|
|
||||||
func _spread_positions(count: int, center_angle: float, arena_r: float) -> Array[Vector3]:
|
|
||||||
var positions: Array[Vector3] = []
|
|
||||||
if count <= 1:
|
|
||||||
positions.append(Vector3(cos(center_angle) * arena_r, 1.0, sin(center_angle) * arena_r))
|
|
||||||
return positions
|
|
||||||
var arc := clampf(TAU * 0.33 + float(count) * 0.25, TAU * 0.33, TAU * 0.92)
|
|
||||||
var slot := arc / float(count)
|
|
||||||
var inner := maxf(2.5, arena_r * 0.5)
|
|
||||||
for i: int in count:
|
|
||||||
# Give each matador its own angular slot for an even fan, but sample the radius
|
|
||||||
# freely across the whole far-side band so the rejection test can push crowded
|
|
||||||
# neighbours onto a different ring instead of piling them at one distance.
|
|
||||||
var base_angle := center_angle - arc * 0.5 + slot * (float(i) + 0.5)
|
|
||||||
var pos := Vector3(cos(base_angle) * arena_r, 1.0, sin(base_angle) * arena_r)
|
|
||||||
for attempt: int in 16:
|
|
||||||
var angle := base_angle + randf_range(-0.5, 0.5) * slot
|
|
||||||
var radius := randf_range(inner, arena_r)
|
|
||||||
pos = Vector3(cos(angle) * radius, 1.0, sin(angle) * radius)
|
|
||||||
if _clear_of(pos, positions, _SPAWN_MIN_SEP):
|
|
||||||
break
|
|
||||||
positions.append(pos)
|
|
||||||
return positions
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn_at(pos: Vector3, generation: int = 0) -> void:
|
|
||||||
var m: Node3D = MATADOR.instantiate()
|
|
||||||
m.position = pos
|
|
||||||
m.set_meta(&"generation", generation)
|
|
||||||
get_parent().add_child(m)
|
|
||||||
m.killed.connect(_on_matador_killed.bind(m))
|
|
||||||
_alive += 1
|
|
||||||
|
|
||||||
|
|
||||||
# A slain matador is replaced by mat_split_factor fresh ones on the spot (a hydra),
|
|
||||||
# so the horde doubles each generation, up to mat_split_generations deep. The bull
|
|
||||||
# wins only when a whole generation dies out without splitting — i.e. once the spawns
|
|
||||||
# stop and the last body drops.
|
|
||||||
func _on_matador_killed(dead: Node3D) -> void:
|
|
||||||
matador_killed.emit()
|
|
||||||
_alive -= 1
|
|
||||||
_kills += 1
|
|
||||||
_split(dead)
|
|
||||||
var win_kills := int(DP.f("mat_win_kills"))
|
|
||||||
var reached_quota := win_kills > 0 and _kills >= win_kills
|
|
||||||
if (reached_quota or _alive <= 0) and not _resolved:
|
|
||||||
_resolved = true
|
|
||||||
all_defeated.emit()
|
|
||||||
|
|
||||||
|
|
||||||
func _split(dead: Node3D) -> void:
|
|
||||||
var count := int(DP.f("mat_split_factor"))
|
|
||||||
var generation := int(dead.get_meta(&"generation", 0))
|
|
||||||
if count < 2 or generation >= int(DP.f("mat_split_generations")):
|
|
||||||
return
|
|
||||||
var arena_r := DP.f("arena_spawn_radius")
|
|
||||||
var origin := dead.global_position
|
|
||||||
origin.y = 0.0
|
|
||||||
var wave: Array[Vector3] = []
|
|
||||||
for i: int in count:
|
|
||||||
var pos := _pick_spawn(origin, arena_r, wave)
|
|
||||||
wave.append(pos)
|
|
||||||
_spawn_at(pos, generation + 1)
|
|
||||||
_last_spawn_positions = wave
|
|
||||||
|
|
||||||
|
|
||||||
# Rejection-sample a spawn point ringed around `origin`, pushing the ring outward on
|
|
||||||
# each failed attempt until it clears both the previous wave (_last_spawn_positions)
|
|
||||||
# and the spots already taken this wave — so a fresh wave never lands where the last
|
|
||||||
# one did. Falls back to the last candidate if the arena is too cramped to satisfy it.
|
|
||||||
func _pick_spawn(origin: Vector3, arena_r: float, taken: Array[Vector3]) -> Vector3:
|
|
||||||
var pos := origin
|
|
||||||
for attempt: int in 12:
|
|
||||||
var angle := randf() * TAU
|
|
||||||
var r := randf_range(2.0, 3.5) + float(attempt) * 0.4
|
|
||||||
pos = origin + Vector3(cos(angle) * r, 0.0, sin(angle) * r)
|
|
||||||
var flat := Vector2(pos.x, pos.z)
|
|
||||||
if flat.length() > arena_r:
|
|
||||||
flat = flat.normalized() * arena_r
|
|
||||||
pos = Vector3(flat.x, 0.0, flat.y)
|
|
||||||
if _clear_of(pos, _last_spawn_positions, _SPAWN_MIN_SEP) \
|
|
||||||
and _clear_of(pos, taken, _SPAWN_MIN_SEP):
|
|
||||||
break
|
|
||||||
pos.y = 1.0
|
|
||||||
return pos
|
|
||||||
|
|
||||||
|
|
||||||
func _clear_of(pos: Vector3, others: Array[Vector3], min_sep: float) -> bool:
|
|
||||||
for o: Vector3 in others:
|
|
||||||
if Vector2(pos.x - o.x, pos.z - o.z).length() < min_sep:
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
## Universal "rotate to landscape" prompt (autoload `OrientationGuard`). The cross-device
|
||||||
|
## half of landscape enforcement: screen.orientation.lock (controls_manager.gd) handles
|
||||||
|
## Android, but iOS Safari has no lock API, and some browsers only lock in fullscreen — so
|
||||||
|
## wherever the device ends up portrait, we simply ask the player to turn it. No canvas
|
||||||
|
## rotation or input remapping (those break touch coordinates), so it works on every phone
|
||||||
|
## and browser cleanly. Only ever shows on touch-primary devices, only while portrait.
|
||||||
|
|
||||||
|
var _panel: ColorRect
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
layer = 128 # above HUD, touch UI, everything
|
||||||
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
|
||||||
|
_panel = ColorRect.new()
|
||||||
|
_panel.color = Color(0.05, 0.03, 0.02, 0.97)
|
||||||
|
_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
_panel.mouse_filter = Control.MOUSE_FILTER_STOP # swallow taps to the game behind it
|
||||||
|
_panel.visible = false
|
||||||
|
add_child(_panel)
|
||||||
|
|
||||||
|
var center := CenterContainer.new()
|
||||||
|
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_panel.add_child(center)
|
||||||
|
|
||||||
|
var box := VBoxContainer.new()
|
||||||
|
box.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
box.add_theme_constant_override("separation", 14)
|
||||||
|
center.add_child(box)
|
||||||
|
|
||||||
|
var title := Label.new()
|
||||||
|
title.text = "Rotate your device"
|
||||||
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
title.add_theme_font_size_override("font_size", 44)
|
||||||
|
title.add_theme_color_override("font_color", Color(1.0, 0.78, 0.22))
|
||||||
|
box.add_child(title)
|
||||||
|
|
||||||
|
var sub := Label.new()
|
||||||
|
sub.text = "Turn it sideways to play in landscape"
|
||||||
|
sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
sub.add_theme_font_size_override("font_size", 22)
|
||||||
|
sub.add_theme_color_override("font_color", Color(0.85, 0.85, 0.85))
|
||||||
|
box.add_child(sub)
|
||||||
|
|
||||||
|
get_viewport().size_changed.connect(_update)
|
||||||
|
_update.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _update() -> void:
|
||||||
|
_panel.visible = _should_guard() and _is_portrait()
|
||||||
|
|
||||||
|
|
||||||
|
func _is_portrait() -> bool:
|
||||||
|
var s := get_viewport().get_visible_rect().size
|
||||||
|
return s.y > s.x
|
||||||
|
|
||||||
|
|
||||||
|
# Touch-primary devices only — never a desktop browser with a tall window (they can't rotate
|
||||||
|
# a monitor). Reuses the same conservative detection the touch UI uses.
|
||||||
|
func _should_guard() -> bool:
|
||||||
|
return Controls.use_touch_ui()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cmh7kd85wi5gx
|
||||||
@@ -14,7 +14,9 @@ const _ANIM_IDLE: StringName = &"Armature|IDLE"
|
|||||||
const _ANIM_KICK: StringName = &"Armature|KICK"
|
const _ANIM_KICK: StringName = &"Armature|KICK"
|
||||||
const _ANIM_SLAM: StringName = &"Armature|SLAM"
|
const _ANIM_SLAM: StringName = &"Armature|SLAM"
|
||||||
const _ANIM_DASH: StringName = &"Armature|DASH"
|
const _ANIM_DASH: StringName = &"Armature|DASH"
|
||||||
|
const _ANIM_WALK: StringName = &"Armature|WALK"
|
||||||
const _ANIM_ROLL: StringName = &"Armature|ROLL"
|
const _ANIM_ROLL: StringName = &"Armature|ROLL"
|
||||||
|
const _ANIM_DEATH: StringName = &"Armature|DEATH"
|
||||||
|
|
||||||
@onready var camera_pivot: Node3D = $Camera3D
|
@onready var camera_pivot: Node3D = $Camera3D
|
||||||
@onready var cube_guy: Node3D = $bull
|
@onready var cube_guy: Node3D = $bull
|
||||||
@@ -77,8 +79,18 @@ var _walk_dust_ramp: Gradient = null
|
|||||||
var _charge_dust_ramp: Gradient = null
|
var _charge_dust_ramp: Gradient = null
|
||||||
|
|
||||||
|
|
||||||
|
const RigidSkin = preload("res://rigid_skin.gd")
|
||||||
|
|
||||||
|
var _hit_flash_tween: Tween
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
add_to_group(&"player")
|
add_to_group(&"player")
|
||||||
|
# Mali/ANGLE mobile GPUs can't run Godot's Compatibility vertex-skinning (transform
|
||||||
|
# feedback) — skinned meshes render invisible on the web build. Rebuild the bull as
|
||||||
|
# non-skinned bone-attached pieces there; desktop keeps smooth GPU skinning.
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
RigidSkin.convert_tree(self)
|
||||||
_max_hp = maxi(1, int(DP.f("bull_max_hp")))
|
_max_hp = maxi(1, int(DP.f("bull_max_hp")))
|
||||||
_hp = _max_hp
|
_hp = _max_hp
|
||||||
_cube_scale = cube_guy.scale
|
_cube_scale = cube_guy.scale
|
||||||
@@ -100,16 +112,16 @@ func _setup_legs() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _setup_hoof_dust() -> void:
|
func _setup_hoof_dust() -> void:
|
||||||
var sphere := _particle_sphere(0.10, 4, 2, _unshaded_material())
|
var sphere := _particle_sphere(0.07, 4, 2, _unshaded_material())
|
||||||
|
|
||||||
_walk_dust_ramp = Gradient.new()
|
_walk_dust_ramp = Gradient.new()
|
||||||
_walk_dust_ramp.set_color(0, Color(0.80, 0.69, 0.46, 1.0))
|
_walk_dust_ramp.set_color(0, Color(0.94, 0.90, 0.83, 1.0))
|
||||||
_walk_dust_ramp.set_color(1, Color(0.80, 0.69, 0.46, 0.0))
|
_walk_dust_ramp.set_color(1, Color(0.94, 0.90, 0.83, 0.0))
|
||||||
|
|
||||||
# Light blue dust while charging
|
# White-beige dust while running
|
||||||
_charge_dust_ramp = Gradient.new()
|
_charge_dust_ramp = Gradient.new()
|
||||||
_charge_dust_ramp.set_color(0, Color(0.20, 0.40, 0.85, 1.0))
|
_charge_dust_ramp.set_color(0, Color(0.94, 0.90, 0.83, 1.0))
|
||||||
_charge_dust_ramp.set_color(1, Color(0.20, 0.40, 0.85, 0.0))
|
_charge_dust_ramp.set_color(1, Color(0.94, 0.90, 0.83, 0.0))
|
||||||
|
|
||||||
var scale_curve := Curve.new()
|
var scale_curve := Curve.new()
|
||||||
scale_curve.add_point(Vector2(0.0, 0.2))
|
scale_curve.add_point(Vector2(0.0, 0.2))
|
||||||
@@ -213,6 +225,7 @@ func _setup_audio() -> void:
|
|||||||
_huff_player = AudioStreamPlayer.new()
|
_huff_player = AudioStreamPlayer.new()
|
||||||
_huff_player.stream = load("res://sounds/bull_huff.ogg")
|
_huff_player.stream = load("res://sounds/bull_huff.ogg")
|
||||||
_huff_player.volume_db = 0.0
|
_huff_player.volume_db = 0.0
|
||||||
|
_huff_player.bus = &"Effects"
|
||||||
add_child(_huff_player)
|
add_child(_huff_player)
|
||||||
if ResourceLoader.exists("res://sounds/crowd_ambient.ogg"):
|
if ResourceLoader.exists("res://sounds/crowd_ambient.ogg"):
|
||||||
_crowd_player = AudioStreamPlayer.new()
|
_crowd_player = AudioStreamPlayer.new()
|
||||||
@@ -222,6 +235,7 @@ func _setup_audio() -> void:
|
|||||||
_crowd_player.stream = s
|
_crowd_player.stream = s
|
||||||
_crowd_player.volume_db = -14.0
|
_crowd_player.volume_db = -14.0
|
||||||
_crowd_player.autoplay = true
|
_crowd_player.autoplay = true
|
||||||
|
_crowd_player.bus = &"Ambient"
|
||||||
add_child(_crowd_player)
|
add_child(_crowd_player)
|
||||||
|
|
||||||
|
|
||||||
@@ -251,8 +265,8 @@ func _set_dust_state(new_state: DustState) -> void:
|
|||||||
p.initial_velocity_min = DP.f("charge_vel_min")
|
p.initial_velocity_min = DP.f("charge_vel_min")
|
||||||
p.initial_velocity_max = DP.f("charge_vel_max")
|
p.initial_velocity_max = DP.f("charge_vel_max")
|
||||||
p.emitting = true
|
p.emitting = true
|
||||||
if _charge_trail_emitter:
|
# Blue energy trail belongs to the roll/dash ability only, not to fast WASD
|
||||||
_charge_trail_emitter.emitting = _dust_state == DustState.CHARGE
|
# running — driven explicitly in the roll loop / _end_roll, not by dust state.
|
||||||
|
|
||||||
|
|
||||||
func _on_dp_changed(_key: String, _val: Variant) -> void:
|
func _on_dp_changed(_key: String, _val: Variant) -> void:
|
||||||
@@ -294,7 +308,7 @@ func _play_idle() -> void:
|
|||||||
return
|
return
|
||||||
_bull_anim.speed_scale = 1.0
|
_bull_anim.speed_scale = 1.0
|
||||||
if _bull_anim.has_animation(_ANIM_IDLE):
|
if _bull_anim.has_animation(_ANIM_IDLE):
|
||||||
_bull_anim.play(_ANIM_IDLE, 0.2)
|
_bull_anim.play(_ANIM_IDLE, 0.05)
|
||||||
|
|
||||||
|
|
||||||
# Drive the WASD locomotion clip: the looping DASH doubles as the run cycle while a
|
# Drive the WASD locomotion clip: the looping DASH doubles as the run cycle while a
|
||||||
@@ -306,9 +320,11 @@ func _update_locomotion_anim(moving: bool) -> void:
|
|||||||
return
|
return
|
||||||
_locomotion_moving = moving
|
_locomotion_moving = moving
|
||||||
if moving:
|
if moving:
|
||||||
if _bull_anim.has_animation(_ANIM_DASH):
|
if _bull_anim.has_animation(_ANIM_WALK):
|
||||||
_bull_anim.speed_scale = 1.0
|
_bull_anim.speed_scale = 2.0
|
||||||
_bull_anim.play(_ANIM_DASH, 0.25)
|
_bull_anim.play(_ANIM_WALK, 1)
|
||||||
|
var anim = _bull_anim.get_animation(_ANIM_WALK)
|
||||||
|
anim.loop_mode = Animation.LOOP_LINEAR
|
||||||
else:
|
else:
|
||||||
_play_idle()
|
_play_idle()
|
||||||
|
|
||||||
@@ -490,6 +506,7 @@ func _tick_roll(delta: float, _steer: Vector3) -> void:
|
|||||||
|
|
||||||
_set_dust_state(DustState.CHARGE)
|
_set_dust_state(DustState.CHARGE)
|
||||||
if _charge_trail_emitter:
|
if _charge_trail_emitter:
|
||||||
|
_charge_trail_emitter.emitting = true
|
||||||
_charge_trail_emitter.direction = (-_roll_dir + Vector3(0.0, 0.25, 0.0)).normalized()
|
_charge_trail_emitter.direction = (-_roll_dir + Vector3(0.0, 0.25, 0.0)).normalized()
|
||||||
|
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
@@ -571,6 +588,8 @@ func _end_roll() -> void:
|
|||||||
_legs.set(&"external_rotation", false)
|
_legs.set(&"external_rotation", false)
|
||||||
if _roll_spark_emitter:
|
if _roll_spark_emitter:
|
||||||
_roll_spark_emitter.emitting = false
|
_roll_spark_emitter.emitting = false
|
||||||
|
if _charge_trail_emitter:
|
||||||
|
_charge_trail_emitter.emitting = false
|
||||||
velocity.x *= 0.15
|
velocity.x *= 0.15
|
||||||
velocity.z *= 0.15
|
velocity.z *= 0.15
|
||||||
_play_idle()
|
_play_idle()
|
||||||
@@ -1022,11 +1041,13 @@ func _physics_process(delta: float) -> void:
|
|||||||
velocity.z = flat_n.z * exit_spd
|
velocity.z = flat_n.z * exit_spd
|
||||||
pre_wall_vel = Vector3(velocity.x, 0.0, velocity.z)
|
pre_wall_vel = Vector3(velocity.x, 0.0, velocity.z)
|
||||||
|
|
||||||
# ── Bounce on landing ─────────────────────────────────────────────────────
|
# ── Landing. Don't bounce ─────────────────────────────────────────────────────
|
||||||
if is_on_floor() and not _was_on_floor:
|
if is_on_floor() and not _was_on_floor:
|
||||||
var impact := -_pre_slide_vel_y
|
velocity.y = 0.0
|
||||||
if impact > DP.f("bounce_min_impact") and not _slam_pending:
|
#Bouncing landing below
|
||||||
velocity.y = impact * DP.f("bounce_restitution")
|
#var impact := -_pre_slide_vel_y
|
||||||
|
#if impact > DP.f("bounce_min_impact") and not _slam_pending:
|
||||||
|
#velocity.y = impact * DP.f("bounce_restitution")
|
||||||
|
|
||||||
# ── Slam landing ──────────────────────────────────────────────────────────
|
# ── Slam landing ──────────────────────────────────────────────────────────
|
||||||
if _slam_pending and is_on_floor() and not _was_on_floor:
|
if _slam_pending and is_on_floor() and not _was_on_floor:
|
||||||
@@ -1073,6 +1094,7 @@ func take_sword_hit(cause: String = "", hit_pos: Vector3 = Vector3.ZERO) -> void
|
|||||||
if _dead or _hit_iframes > 0.0:
|
if _dead or _hit_iframes > 0.0:
|
||||||
return
|
return
|
||||||
_hp -= 1
|
_hp -= 1
|
||||||
|
_hit_flash()
|
||||||
_hit_iframes = DP.f("bull_hit_iframes")
|
_hit_iframes = DP.f("bull_hit_iframes")
|
||||||
health_changed.emit(_hp, _max_hp)
|
health_changed.emit(_hp, _max_hp)
|
||||||
hit_taken.emit(cause)
|
hit_taken.emit(cause)
|
||||||
@@ -1085,6 +1107,9 @@ func take_sword_hit(cause: String = "", hit_pos: Vector3 = Vector3.ZERO) -> void
|
|||||||
_huff_player.pitch_scale = randf_range(0.7, 0.9)
|
_huff_player.pitch_scale = randf_range(0.7, 0.9)
|
||||||
_huff_player.play()
|
_huff_player.play()
|
||||||
if _hp <= 0:
|
if _hp <= 0:
|
||||||
|
if _bull_anim.current_animation != _ANIM_DEATH:
|
||||||
|
_bull_anim.speed_scale = 0.8
|
||||||
|
_bull_anim.play(_ANIM_DEATH)
|
||||||
_dead = true
|
_dead = true
|
||||||
died.emit(cause)
|
died.emit(cause)
|
||||||
|
|
||||||
@@ -1130,3 +1155,36 @@ func _spawn_blood(hit_pos: Vector3) -> void:
|
|||||||
p.global_position = hit_pos
|
p.global_position = hit_pos
|
||||||
p.restart()
|
p.restart()
|
||||||
_free_after(p, 1.5)
|
_free_after(p, 1.5)
|
||||||
|
|
||||||
|
|
||||||
|
#hit flashing
|
||||||
|
func _hit_flash() -> void:
|
||||||
|
var meshes := find_children("*", "MeshInstance3D", true, false)
|
||||||
|
|
||||||
|
for node in meshes:
|
||||||
|
var mesh := node as MeshInstance3D
|
||||||
|
if mesh:
|
||||||
|
mesh.material_overlay = _make_hit_overlay()
|
||||||
|
|
||||||
|
if _hit_flash_tween:
|
||||||
|
_hit_flash_tween.kill()
|
||||||
|
|
||||||
|
_hit_flash_tween = create_tween()
|
||||||
|
_hit_flash_tween.tween_interval(0.08)
|
||||||
|
_hit_flash_tween.tween_callback(_clear_hit_flash)
|
||||||
|
|
||||||
|
|
||||||
|
func _make_hit_overlay() -> StandardMaterial3D:
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.albedo_color = Color(1.0, 0.03, 0.03, 1.0)
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
func _clear_hit_flash() -> void:
|
||||||
|
var meshes := find_children("*", "MeshInstance3D", true, false)
|
||||||
|
|
||||||
|
for node in meshes:
|
||||||
|
var mesh := node as MeshInstance3D
|
||||||
|
if mesh:
|
||||||
|
mesh.material_overlay = null
|
||||||
|
|||||||
@@ -13,13 +13,11 @@ config_version=5
|
|||||||
config/name="Bullosseum"
|
config/name="Bullosseum"
|
||||||
config/version="0.2.0"
|
config/version="0.2.0"
|
||||||
run/main_scene="res://MainMenu.tscn"
|
run/main_scene="res://MainMenu.tscn"
|
||||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||||
config/icon="res://icon.svg"
|
|
||||||
config/run/main_scene="res://MainMenu.tscn"
|
|
||||||
boot_splash/bg_color=Color(0, 0, 0, 1)
|
boot_splash/bg_color=Color(0, 0, 0, 1)
|
||||||
boot_splash/image="res://Assets/loading_screen.png"
|
boot_splash/image="res://Assets/loading_screen.png"
|
||||||
boot_splash/fullsize=true
|
config/icon="uid://dct1d88g015el"
|
||||||
boot_splash/use_filter=true
|
config/run/main_scene="res://MainMenu.tscn"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
@@ -28,6 +26,13 @@ Console="*res://console.gd"
|
|||||||
Controls="*res://controls_manager.gd"
|
Controls="*res://controls_manager.gd"
|
||||||
DebugDraw="*res://debug_draw.gd"
|
DebugDraw="*res://debug_draw.gd"
|
||||||
Settings="*res://settings.gd"
|
Settings="*res://settings.gd"
|
||||||
|
Run="*res://levels/run_state.gd"
|
||||||
|
OrientationGuard="*res://orientation_guard.gd"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/mode=3
|
||||||
|
window/handheld/orientation=4
|
||||||
|
|
||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
@@ -55,7 +60,7 @@ move_left={
|
|||||||
"deadzone": 0.2,
|
"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||||
, 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":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
, 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":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -63,18 +68,20 @@ move_right={
|
|||||||
"deadzone": 0.2,
|
"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||||
, 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":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
, 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":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
zoom_in={
|
zoom_in={
|
||||||
"deadzone": 0.2,
|
"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":8,"position":Vector2(726, 8),"global_position":Vector2(735, 56),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
"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":8,"position":Vector2(726, 8),"global_position":Vector2(735, 56),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
zoom_out={
|
zoom_out={
|
||||||
"deadzone": 0.2,
|
"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":16,"position":Vector2(400, 21),"global_position":Vector2(409, 69),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
"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":16,"position":Vector2(400, 21),"global_position":Vector2(409, 69),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
toggle_mouse_capture={
|
toggle_mouse_capture={
|
||||||
@@ -90,14 +97,14 @@ ability_kick={
|
|||||||
}
|
}
|
||||||
ability_slam={
|
ability_slam={
|
||||||
"deadzone": 0.2,
|
"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":71,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
|
"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":72,"key_label":0,"unicode":104,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
ability_dash={
|
ability_dash={
|
||||||
"deadzone": 0.2,
|
"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":72,"key_label":0,"unicode":104,"location":0,"echo":false,"script":null)
|
"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":71,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
ability_roll={
|
ability_roll={
|
||||||
@@ -117,5 +124,7 @@ pointing/emulate_touch_from_mouse=true
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
rendering_device/driver.windows="d3d12"
|
rendering_device/driver.windows="opengl3"
|
||||||
anti_aliasing/quality/use_taa=true
|
renderer/rendering_method="gl_compatibility"
|
||||||
|
textures/vram_compression/import_s3tc_bptc=true
|
||||||
|
textures/vram_compression/import_etc2_astc=true
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
extends RefCounted
|
||||||
|
## Converts Skeleton3D-skinned meshes into NON-skinned mesh pieces parented to
|
||||||
|
## BoneAttachment3D nodes, so rendering never uses GPU vertex-skinning (transform
|
||||||
|
## feedback). Required on ANGLE/Vulkan/Mali mobile GPUs (e.g. Mali-G720 in mobile
|
||||||
|
## Chrome/Brave), where Godot's Compatibility skinning path renders skinned meshes
|
||||||
|
## invisible while non-skinned meshes draw fine.
|
||||||
|
##
|
||||||
|
## Each triangle is assigned wholesale to its dominant bone (highest summed weight),
|
||||||
|
## so segments stay watertight; joints become rigid (no smooth bending), which suits
|
||||||
|
## the low-poly look. Call RigidSkin.convert_tree(character_root) once after the scene
|
||||||
|
## is instanced (bones don't need to be posed yet — pieces are baked in bind space).
|
||||||
|
|
||||||
|
static func convert_tree(root: Node) -> int:
|
||||||
|
var converted := 0
|
||||||
|
for skel: Skeleton3D in _find(root, "Skeleton3D", []):
|
||||||
|
for mi: Node in skel.get_children():
|
||||||
|
if mi is MeshInstance3D and (mi as MeshInstance3D).skin != null \
|
||||||
|
and (mi as MeshInstance3D).mesh is ArrayMesh:
|
||||||
|
if _convert(skel, mi as MeshInstance3D):
|
||||||
|
converted += 1
|
||||||
|
return converted
|
||||||
|
|
||||||
|
|
||||||
|
static func _find(n: Node, klass: String, acc: Array) -> Array:
|
||||||
|
if n.is_class(klass):
|
||||||
|
acc.append(n)
|
||||||
|
for c: Node in n.get_children():
|
||||||
|
_find(c, klass, acc)
|
||||||
|
return acc
|
||||||
|
|
||||||
|
|
||||||
|
static func _convert(skel: Skeleton3D, mi: MeshInstance3D) -> bool:
|
||||||
|
var mesh := mi.mesh as ArrayMesh
|
||||||
|
var skin := mi.skin
|
||||||
|
|
||||||
|
# bind index -> (bone index, bind pose = mesh-space -> bone-local)
|
||||||
|
var bind_bone: PackedInt32Array = []
|
||||||
|
var bind_pose: Array[Transform3D] = []
|
||||||
|
for b: int in skin.get_bind_count():
|
||||||
|
var bone := skin.get_bind_bone(b)
|
||||||
|
if bone < 0:
|
||||||
|
bone = skel.find_bone(skin.get_bind_name(b))
|
||||||
|
bind_bone.append(bone)
|
||||||
|
bind_pose.append(skin.get_bind_pose(b))
|
||||||
|
|
||||||
|
# Accumulate SurfaceTool geometry per destination bone.
|
||||||
|
var st_by_bone: Dictionary = {} # bone index -> SurfaceTool
|
||||||
|
var mat_by_bone: Dictionary = {} # bone index -> Material (first seen)
|
||||||
|
|
||||||
|
for s: int in mesh.get_surface_count():
|
||||||
|
var arr := mesh.surface_get_arrays(s)
|
||||||
|
var verts: PackedVector3Array = arr[Mesh.ARRAY_VERTEX]
|
||||||
|
var norms: PackedVector3Array = arr[Mesh.ARRAY_NORMAL]
|
||||||
|
var uvs: PackedVector2Array = arr[Mesh.ARRAY_TEX_UV] if arr[Mesh.ARRAY_TEX_UV] != null else PackedVector2Array()
|
||||||
|
var cols: PackedColorArray = arr[Mesh.ARRAY_COLOR] if arr[Mesh.ARRAY_COLOR] != null else PackedColorArray()
|
||||||
|
var bones: PackedInt32Array = arr[Mesh.ARRAY_BONES]
|
||||||
|
var weights: PackedFloat32Array = arr[Mesh.ARRAY_WEIGHTS]
|
||||||
|
var idx: PackedInt32Array = arr[Mesh.ARRAY_INDEX]
|
||||||
|
var mat := mesh.surface_get_material(s)
|
||||||
|
var infl := 8 if (mesh.surface_get_format(s) & Mesh.ARRAY_FLAG_USE_8_BONE_WEIGHTS) else 4
|
||||||
|
|
||||||
|
var tri := PackedInt32Array()
|
||||||
|
if idx != null and idx.size() > 0:
|
||||||
|
tri = idx
|
||||||
|
else:
|
||||||
|
tri.resize(verts.size())
|
||||||
|
for i: int in verts.size():
|
||||||
|
tri[i] = i
|
||||||
|
|
||||||
|
for t: int in range(0, tri.size(), 3):
|
||||||
|
var a := tri[t]
|
||||||
|
var b := tri[t + 1]
|
||||||
|
var c := tri[t + 2]
|
||||||
|
var bind := _dominant_bind([a, b, c], bones, weights, infl)
|
||||||
|
var bone := bind_bone[bind]
|
||||||
|
var pose := bind_pose[bind]
|
||||||
|
var st: SurfaceTool = st_by_bone.get(bone)
|
||||||
|
if st == null:
|
||||||
|
st = SurfaceTool.new()
|
||||||
|
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||||
|
st_by_bone[bone] = st
|
||||||
|
mat_by_bone[bone] = mat
|
||||||
|
for v: int in [a, b, c]:
|
||||||
|
if cols.size() > v:
|
||||||
|
st.set_color(cols[v])
|
||||||
|
if uvs.size() > v:
|
||||||
|
st.set_uv(uvs[v])
|
||||||
|
if norms.size() > v:
|
||||||
|
st.set_normal((pose.basis * norms[v]).normalized())
|
||||||
|
st.add_vertex(pose * verts[v])
|
||||||
|
|
||||||
|
if st_by_bone.is_empty():
|
||||||
|
return false
|
||||||
|
|
||||||
|
for bone: int in st_by_bone:
|
||||||
|
var att := BoneAttachment3D.new()
|
||||||
|
att.bone_name = skel.get_bone_name(bone)
|
||||||
|
skel.add_child(att)
|
||||||
|
var piece := MeshInstance3D.new()
|
||||||
|
var st: SurfaceTool = st_by_bone[bone]
|
||||||
|
var m := st.commit()
|
||||||
|
if mat_by_bone[bone] != null:
|
||||||
|
m.surface_set_material(0, mat_by_bone[bone])
|
||||||
|
piece.mesh = m
|
||||||
|
att.add_child(piece)
|
||||||
|
|
||||||
|
# Hide (don't free) the original: keeps the node for any gameplay code that references
|
||||||
|
# it, and a hidden skinned mesh isn't drawn so it never hits the transform-feedback path.
|
||||||
|
mi.visible = false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
static func _dominant_bind(vs: Array, bones: PackedInt32Array, weights: PackedFloat32Array, infl: int) -> int:
|
||||||
|
var acc: Dictionary = {}
|
||||||
|
for v: int in vs:
|
||||||
|
for k: int in infl:
|
||||||
|
var w := weights[v * infl + k]
|
||||||
|
if w <= 0.0:
|
||||||
|
continue
|
||||||
|
var bind := bones[v * infl + k]
|
||||||
|
acc[bind] = float(acc.get(bind, 0.0)) + w
|
||||||
|
var best := 0
|
||||||
|
var best_w := -1.0
|
||||||
|
for bind: int in acc:
|
||||||
|
if acc[bind] > best_w:
|
||||||
|
best_w = acc[bind]
|
||||||
|
best = bind
|
||||||
|
return best
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dymd8vcfmtjts
|
||||||
@@ -8,7 +8,7 @@ SKIP_GAMEPLAY=0
|
|||||||
for arg in "$@"; do [[ "$arg" == "--no-gameplay" ]] && SKIP_GAMEPLAY=1; done
|
for arg in "$@"; do [[ "$arg" == "--no-gameplay" ]] && SKIP_GAMEPLAY=1; done
|
||||||
|
|
||||||
echo "=== gdlint ==="
|
echo "=== gdlint ==="
|
||||||
gdlint *.gd tests/*.gd
|
gdlint *.gd levels/*.gd tests/*.gd
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Logic tests ==="
|
echo "=== Logic tests ==="
|
||||||
@@ -18,6 +18,10 @@ echo ""
|
|||||||
echo "=== Console focus tests ==="
|
echo "=== Console focus tests ==="
|
||||||
"$GODOT" --headless --script tests/console_focus_test.gd
|
"$GODOT" --headless --script tests/console_focus_test.gd
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Level switch tests (no cross-level geometry/collider leaks) ==="
|
||||||
|
"$GODOT" --headless --script tests/level_switch_test.gd
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Performance tests (frame-budget regression guard) ==="
|
echo "=== Performance tests (frame-budget regression guard) ==="
|
||||||
"$GODOT" --headless --script tests/performance_test.gd
|
"$GODOT" --headless --script tests/performance_test.gd
|
||||||
|
|||||||