diff --git a/assets/animations/Autoattack.fbx b/assets/Warrior+Animation/Autoattack.fbx similarity index 100% rename from assets/animations/Autoattack.fbx rename to assets/Warrior+Animation/Autoattack.fbx diff --git a/assets/animations/Autoattack.fbx.import b/assets/Warrior+Animation/Autoattack.fbx.import similarity index 77% rename from assets/animations/Autoattack.fbx.import rename to assets/Warrior+Animation/Autoattack.fbx.import index a9af76e..d6a1703 100644 --- a/assets/animations/Autoattack.fbx.import +++ b/assets/Warrior+Animation/Autoattack.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://cfmx5li2h2gsb" -path="res://.godot/imported/Autoattack.fbx-3670ec5144a5943b8d9ee95f7186efd3.scn" +uid="uid://vo540cksibhf" +path="res://.godot/imported/Autoattack.fbx-53cd4a5b8eaf2a74c3bff45c47238010.scn" [deps] -source_file="res://assets/animations/Autoattack.fbx" -dest_files=["res://.godot/imported/Autoattack.fbx-3670ec5144a5943b8d9ee95f7186efd3.scn"] +source_file="res://assets/Warrior+Animation/Autoattack.fbx" +dest_files=["res://.godot/imported/Autoattack.fbx-53cd4a5b8eaf2a74c3bff45c47238010.scn"] [params] diff --git a/assets/animations/Dying Backwards.fbx b/assets/Warrior+Animation/Dying Backwards.fbx similarity index 100% rename from assets/animations/Dying Backwards.fbx rename to assets/Warrior+Animation/Dying Backwards.fbx diff --git a/assets/animations/Dying Backwards.fbx.import b/assets/Warrior+Animation/Dying Backwards.fbx.import similarity index 75% rename from assets/animations/Dying Backwards.fbx.import rename to assets/Warrior+Animation/Dying Backwards.fbx.import index a1aeb61..673511b 100644 --- a/assets/animations/Dying Backwards.fbx.import +++ b/assets/Warrior+Animation/Dying Backwards.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://dpm8k57akvg3k" -path="res://.godot/imported/Dying Backwards.fbx-77c7ef392c2c96235d9f4f9f7c331de8.scn" +uid="uid://b3n7056tyskib" +path="res://.godot/imported/Dying Backwards.fbx-f2b9c7b8a945c1b32b59e6174df75635.scn" [deps] -source_file="res://assets/animations/Dying Backwards.fbx" -dest_files=["res://.godot/imported/Dying Backwards.fbx-77c7ef392c2c96235d9f4f9f7c331de8.scn"] +source_file="res://assets/Warrior+Animation/Dying Backwards.fbx" +dest_files=["res://.godot/imported/Dying Backwards.fbx-f2b9c7b8a945c1b32b59e6174df75635.scn"] [params] diff --git a/assets/animations/Heavy Strike.fbx b/assets/Warrior+Animation/Heavy Strike.fbx similarity index 100% rename from assets/animations/Heavy Strike.fbx rename to assets/Warrior+Animation/Heavy Strike.fbx diff --git a/assets/animations/Heavy Strike.fbx.import b/assets/Warrior+Animation/Heavy Strike.fbx.import similarity index 76% rename from assets/animations/Heavy Strike.fbx.import rename to assets/Warrior+Animation/Heavy Strike.fbx.import index 0328ac1..4c1bba8 100644 --- a/assets/animations/Heavy Strike.fbx.import +++ b/assets/Warrior+Animation/Heavy Strike.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://cmpv0gty3rcm6" -path="res://.godot/imported/Heavy Strike.fbx-5d5103d3203a6a38edd177bdd35ccb16.scn" +uid="uid://r25qslfmbg4s" +path="res://.godot/imported/Heavy Strike.fbx-f9eed104269d2005bd3440c7e91ac7ea.scn" [deps] -source_file="res://assets/animations/Heavy Strike.fbx" -dest_files=["res://.godot/imported/Heavy Strike.fbx-5d5103d3203a6a38edd177bdd35ccb16.scn"] +source_file="res://assets/Warrior+Animation/Heavy Strike.fbx" +dest_files=["res://.godot/imported/Heavy Strike.fbx-f9eed104269d2005bd3440c7e91ac7ea.scn"] [params] diff --git a/assets/Warrior+Animation/Quick Roll To Run.fbx b/assets/Warrior+Animation/Quick Roll To Run.fbx new file mode 100644 index 0000000..e7640ba Binary files /dev/null and b/assets/Warrior+Animation/Quick Roll To Run.fbx differ diff --git a/assets/animations/Left Strafe Walking.fbx.import b/assets/Warrior+Animation/Quick Roll To Run.fbx.import similarity index 75% rename from assets/animations/Left Strafe Walking.fbx.import rename to assets/Warrior+Animation/Quick Roll To Run.fbx.import index c604b16..e725eff 100644 --- a/assets/animations/Left Strafe Walking.fbx.import +++ b/assets/Warrior+Animation/Quick Roll To Run.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://wurcf4qd6sn1" -path="res://.godot/imported/Left Strafe Walking.fbx-918d1a60e568dd43e53b31199b3f57d3.scn" +uid="uid://ctup6ycp8fuqk" +path="res://.godot/imported/Quick Roll To Run.fbx-b02b0c01f60ef724d35b77bd3136b689.scn" [deps] -source_file="res://assets/animations/Left Strafe Walking.fbx" -dest_files=["res://.godot/imported/Left Strafe Walking.fbx-918d1a60e568dd43e53b31199b3f57d3.scn"] +source_file="res://assets/Warrior+Animation/Quick Roll To Run.fbx" +dest_files=["res://.godot/imported/Quick Roll To Run.fbx-b02b0c01f60ef724d35b77bd3136b689.scn"] [params] diff --git a/assets/Warrior+Animation/Running Jump.fbx b/assets/Warrior+Animation/Running Jump.fbx new file mode 100644 index 0000000..6a2266a Binary files /dev/null and b/assets/Warrior+Animation/Running Jump.fbx differ diff --git a/assets/Warrior+Animation/Running Jump.fbx.import b/assets/Warrior+Animation/Running Jump.fbx.import new file mode 100644 index 0000000..42af86f --- /dev/null +++ b/assets/Warrior+Animation/Running Jump.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bhte2kawlr8bv" +path="res://.godot/imported/Running Jump.fbx-02348a505e41bceab5ba1604094098b3.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/Running Jump.fbx" +dest_files=["res://.godot/imported/Running Jump.fbx-02348a505e41bceab5ba1604094098b3.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/Running Left Strafe.fbx b/assets/Warrior+Animation/Running Left Strafe.fbx new file mode 100644 index 0000000..cd3efbf Binary files /dev/null and b/assets/Warrior+Animation/Running Left Strafe.fbx differ diff --git a/assets/animations/Right Strafe Walking.fbx.import b/assets/Warrior+Animation/Running Left Strafe.fbx.import similarity index 75% rename from assets/animations/Right Strafe Walking.fbx.import rename to assets/Warrior+Animation/Running Left Strafe.fbx.import index cf65000..82e88fb 100644 --- a/assets/animations/Right Strafe Walking.fbx.import +++ b/assets/Warrior+Animation/Running Left Strafe.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://bhv5ig8xnw4v0" -path="res://.godot/imported/Right Strafe Walking.fbx-f1318b812bb12a00a11ec8112c1f6855.scn" +uid="uid://gkl3ij5dvghc" +path="res://.godot/imported/Running Left Strafe.fbx-f8ffe3f38c4c7e18db7a5cb37788743e.scn" [deps] -source_file="res://assets/animations/Right Strafe Walking.fbx" -dest_files=["res://.godot/imported/Right Strafe Walking.fbx-f1318b812bb12a00a11ec8112c1f6855.scn"] +source_file="res://assets/Warrior+Animation/Running Left Strafe.fbx" +dest_files=["res://.godot/imported/Running Left Strafe.fbx-f8ffe3f38c4c7e18db7a5cb37788743e.scn"] [params] diff --git a/assets/Warrior+Animation/Running Right Strafe run.fbx b/assets/Warrior+Animation/Running Right Strafe run.fbx new file mode 100644 index 0000000..0931c9e Binary files /dev/null and b/assets/Warrior+Animation/Running Right Strafe run.fbx differ diff --git a/assets/Warrior+Animation/Running Right Strafe run.fbx.import b/assets/Warrior+Animation/Running Right Strafe run.fbx.import new file mode 100644 index 0000000..6fa0a09 --- /dev/null +++ b/assets/Warrior+Animation/Running Right Strafe run.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://lm2ofk411i4" +path="res://.godot/imported/Running Right Strafe run.fbx-31b53a15a609d5d0f6711845d5941119.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/Running Right Strafe run.fbx" +dest_files=["res://.godot/imported/Running Right Strafe run.fbx-31b53a15a609d5d0f6711845d5941119.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/Running Turn 180.fbx b/assets/Warrior+Animation/Running Turn 180.fbx new file mode 100644 index 0000000..6cb7e1d Binary files /dev/null and b/assets/Warrior+Animation/Running Turn 180.fbx differ diff --git a/assets/Warrior+Animation/Running Turn 180.fbx.import b/assets/Warrior+Animation/Running Turn 180.fbx.import new file mode 100644 index 0000000..eead0a8 --- /dev/null +++ b/assets/Warrior+Animation/Running Turn 180.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://7j8e63c4gtt8" +path="res://.godot/imported/Running Turn 180.fbx-863af9daa2d6c9284445a0bd16da3f45.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/Running Turn 180.fbx" +dest_files=["res://.godot/imported/Running Turn 180.fbx-863af9daa2d6c9284445a0bd16da3f45.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/animations/Walking Backwards.fbx b/assets/Warrior+Animation/Walking Backwards.fbx similarity index 100% rename from assets/animations/Walking Backwards.fbx rename to assets/Warrior+Animation/Walking Backwards.fbx diff --git a/assets/animations/Walking Backwards.fbx.import b/assets/Warrior+Animation/Walking Backwards.fbx.import similarity index 75% rename from assets/animations/Walking Backwards.fbx.import rename to assets/Warrior+Animation/Walking Backwards.fbx.import index 4a51524..12f9479 100644 --- a/assets/animations/Walking Backwards.fbx.import +++ b/assets/Warrior+Animation/Walking Backwards.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://bck2nreqc2gb3" -path="res://.godot/imported/Walking Backwards.fbx-485a35ec4e48207190c0e10717297ab7.scn" +uid="uid://1unm2efm7s3f" +path="res://.godot/imported/Walking Backwards.fbx-cf1183868a4d9d213ebca12d53b3ae81.scn" [deps] -source_file="res://assets/animations/Walking Backwards.fbx" -dest_files=["res://.godot/imported/Walking Backwards.fbx-485a35ec4e48207190c0e10717297ab7.scn"] +source_file="res://assets/Warrior+Animation/Walking Backwards.fbx" +dest_files=["res://.godot/imported/Walking Backwards.fbx-cf1183868a4d9d213ebca12d53b3ae81.scn"] [params] diff --git a/assets/Warrior+Animation/Walking Jump.fbx b/assets/Warrior+Animation/Walking Jump.fbx new file mode 100644 index 0000000..a745adf Binary files /dev/null and b/assets/Warrior+Animation/Walking Jump.fbx differ diff --git a/assets/Warrior+Animation/Walking Jump.fbx.import b/assets/Warrior+Animation/Walking Jump.fbx.import new file mode 100644 index 0000000..a6a2598 --- /dev/null +++ b/assets/Warrior+Animation/Walking Jump.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cs35mwwqnitrp" +path="res://.godot/imported/Walking Jump.fbx-327d0240715eb7b17d5a21fdf85a64e6.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/Walking Jump.fbx" +dest_files=["res://.godot/imported/Walking Jump.fbx-327d0240715eb7b17d5a21fdf85a64e6.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/castle_guard_01.fbx.import b/assets/Warrior+Animation/castle_guard_01.fbx.import new file mode 100644 index 0000000..9ac480c --- /dev/null +++ b/assets/Warrior+Animation/castle_guard_01.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://daeym1tdcnhhd" +path="res://.godot/imported/castle_guard_01.fbx-799eb31d8eba8e9d75bc4ca0ebabde43.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/castle_guard_01.fbx" +dest_files=["res://.godot/imported/castle_guard_01.fbx-799eb31d8eba8e9d75bc4ca0ebabde43.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/animations/Walking_0.png b/assets/Warrior+Animation/castle_guard_01_0.png similarity index 100% rename from assets/animations/Walking_0.png rename to assets/Warrior+Animation/castle_guard_01_0.png diff --git a/assets/animations/Walking_0.png.import b/assets/Warrior+Animation/castle_guard_01_0.png.import similarity index 73% rename from assets/animations/Walking_0.png.import rename to assets/Warrior+Animation/castle_guard_01_0.png.import index 5b39a16..252f9bf 100644 --- a/assets/animations/Walking_0.png.import +++ b/assets/Warrior+Animation/castle_guard_01_0.png.import @@ -2,8 +2,8 @@ importer="texture" type="CompressedTexture2D" -uid="uid://clc13cbh72eov" -path.s3tc="res://.godot/imported/Walking_0.png-04bee5fb7233febb0f01a436248762b5.s3tc.ctex" +uid="uid://d07eqssia5uce" +path.s3tc="res://.godot/imported/castle_guard_01_0.png-64069e01a3d6f24be61f8b235530fefe.s3tc.ctex" metadata={ "imported_formats": ["s3tc_bptc"], "vram_texture": true @@ -14,8 +14,8 @@ generator_parameters={ [deps] -source_file="res://assets/animations/Walking_0.png" -dest_files=["res://.godot/imported/Walking_0.png-04bee5fb7233febb0f01a436248762b5.s3tc.ctex"] +source_file="res://assets/Warrior+Animation/castle_guard_01_0.png" +dest_files=["res://.godot/imported/castle_guard_01_0.png-64069e01a3d6f24be61f8b235530fefe.s3tc.ctex"] [params] diff --git a/assets/animations/Walking_1.png b/assets/Warrior+Animation/castle_guard_01_1.png similarity index 100% rename from assets/animations/Walking_1.png rename to assets/Warrior+Animation/castle_guard_01_1.png diff --git a/assets/animations/Walking_1.png.import b/assets/Warrior+Animation/castle_guard_01_1.png.import similarity index 73% rename from assets/animations/Walking_1.png.import rename to assets/Warrior+Animation/castle_guard_01_1.png.import index 056df0e..45551a4 100644 --- a/assets/animations/Walking_1.png.import +++ b/assets/Warrior+Animation/castle_guard_01_1.png.import @@ -2,8 +2,8 @@ importer="texture" type="CompressedTexture2D" -uid="uid://dphpf7qa3hkvh" -path="res://.godot/imported/Walking_1.png-ba1e8e03f6c24586962fc1b62dccdfe9.ctex" +uid="uid://b3ofnfnpmjbv4" +path="res://.godot/imported/castle_guard_01_1.png-cc0fe514ff22e36663b36dadc87f514d.ctex" metadata={ "vram_texture": false } @@ -13,8 +13,8 @@ generator_parameters={ [deps] -source_file="res://assets/animations/Walking_1.png" -dest_files=["res://.godot/imported/Walking_1.png-ba1e8e03f6c24586962fc1b62dccdfe9.ctex"] +source_file="res://assets/Warrior+Animation/castle_guard_01_1.png" +dest_files=["res://.godot/imported/castle_guard_01_1.png-cc0fe514ff22e36663b36dadc87f514d.ctex"] [params] diff --git a/assets/animations/Walking_2.png b/assets/Warrior+Animation/castle_guard_01_2.png similarity index 100% rename from assets/animations/Walking_2.png rename to assets/Warrior+Animation/castle_guard_01_2.png diff --git a/assets/models/warrior_2.png.import b/assets/Warrior+Animation/castle_guard_01_2.png.import similarity index 67% rename from assets/models/warrior_2.png.import rename to assets/Warrior+Animation/castle_guard_01_2.png.import index a3cf4ee..af027cf 100644 --- a/assets/models/warrior_2.png.import +++ b/assets/Warrior+Animation/castle_guard_01_2.png.import @@ -2,8 +2,8 @@ importer="texture" type="CompressedTexture2D" -uid="uid://coqdhbh5uwkjf" -path.s3tc="res://.godot/imported/warrior_2.png-0164e54eb7a56547f2b489b55a55ca25.s3tc.ctex" +uid="uid://cgqa0kap7nde7" +path.s3tc="res://.godot/imported/castle_guard_01_2.png-7d061f5d8fcddd99f52285a75a1f6982.s3tc.ctex" metadata={ "imported_formats": ["s3tc_bptc"], "vram_texture": true @@ -14,8 +14,8 @@ generator_parameters={ [deps] -source_file="res://assets/models/warrior_2.png" -dest_files=["res://.godot/imported/warrior_2.png-0164e54eb7a56547f2b489b55a55ca25.s3tc.ctex"] +source_file="res://assets/Warrior+Animation/castle_guard_01_2.png" +dest_files=["res://.godot/imported/castle_guard_01_2.png-7d061f5d8fcddd99f52285a75a1f6982.s3tc.ctex"] [params] @@ -30,7 +30,7 @@ compress/channel_pack=0 mipmaps/generate=true mipmaps/limit=-1 roughness/mode=1 -roughness/src_normal="res://assets/models/warrior_2.png" +roughness/src_normal="res://assets/Warrior+Animation/castle_guard_01_2.png" process/channel_remap/red=0 process/channel_remap/green=1 process/channel_remap/blue=2 diff --git a/assets/models/warrior.fbx.import b/assets/Warrior+Animation/idle.fbx.import similarity index 78% rename from assets/models/warrior.fbx.import rename to assets/Warrior+Animation/idle.fbx.import index 8ddc5b7..48eb104 100644 --- a/assets/models/warrior.fbx.import +++ b/assets/Warrior+Animation/idle.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://da1w523lg7i2b" -path="res://.godot/imported/warrior.fbx-b4acdad06881ff9e3d6bb32ec03761c9.scn" +uid="uid://bykl7cq8qx0i" +path="res://.godot/imported/idle.fbx-6a27ea4d436753d86355f51c515589cc.scn" [deps] -source_file="res://assets/models/warrior.fbx" -dest_files=["res://.godot/imported/warrior.fbx-b4acdad06881ff9e3d6bb32ec03761c9.scn"] +source_file="res://assets/Warrior+Animation/idle.fbx" +dest_files=["res://.godot/imported/idle.fbx-6a27ea4d436753d86355f51c515589cc.scn"] [params] diff --git a/assets/animations/Idle.fbx.import b/assets/Warrior+Animation/jump.fbx.import similarity index 78% rename from assets/animations/Idle.fbx.import rename to assets/Warrior+Animation/jump.fbx.import index 105a47b..5890d82 100644 --- a/assets/animations/Idle.fbx.import +++ b/assets/Warrior+Animation/jump.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://cxr7xlsq3vvhi" -path="res://.godot/imported/Idle.fbx-ecf8c7ec9efbd10accf3a620e1992827.scn" +uid="uid://kmypwcf75mm5" +path="res://.godot/imported/jump.fbx-4653cae9b7a0a7fea2fdbf8b3432de58.scn" [deps] -source_file="res://assets/animations/Idle.fbx" -dest_files=["res://.godot/imported/Idle.fbx-ecf8c7ec9efbd10accf3a620e1992827.scn"] +source_file="res://assets/Warrior+Animation/jump.fbx" +dest_files=["res://.godot/imported/jump.fbx-4653cae9b7a0a7fea2fdbf8b3432de58.scn"] [params] diff --git a/assets/Warrior+Animation/left strafe walking.fbx.import b/assets/Warrior+Animation/left strafe walking.fbx.import new file mode 100644 index 0000000..614c713 --- /dev/null +++ b/assets/Warrior+Animation/left strafe walking.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://r3rpi8fcmkne" +path="res://.godot/imported/left strafe walking.fbx-5f9622a45ca2758ee53f10a63deb0dde.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/left strafe walking.fbx" +dest_files=["res://.godot/imported/left strafe walking.fbx-5f9622a45ca2758ee53f10a63deb0dde.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/animations/Start Walking.fbx.import b/assets/Warrior+Animation/left strafe.fbx.import similarity index 76% rename from assets/animations/Start Walking.fbx.import rename to assets/Warrior+Animation/left strafe.fbx.import index a7389b6..60d4baf 100644 --- a/assets/animations/Start Walking.fbx.import +++ b/assets/Warrior+Animation/left strafe.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://bjwb78njpjo2d" -path="res://.godot/imported/Start Walking.fbx-c54b281554787bf73bcae8159e84a930.scn" +uid="uid://x3sux8evka5y" +path="res://.godot/imported/left strafe.fbx-123d2901134abff2b2af22577a6143ca.scn" [deps] -source_file="res://assets/animations/Start Walking.fbx" -dest_files=["res://.godot/imported/Start Walking.fbx-c54b281554787bf73bcae8159e84a930.scn"] +source_file="res://assets/Warrior+Animation/left strafe.fbx" +dest_files=["res://.godot/imported/left strafe.fbx-123d2901134abff2b2af22577a6143ca.scn"] [params] diff --git a/assets/Warrior+Animation/left turn 90.fbx.import b/assets/Warrior+Animation/left turn 90.fbx.import new file mode 100644 index 0000000..12146e8 --- /dev/null +++ b/assets/Warrior+Animation/left turn 90.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bfn8s86o81t86" +path="res://.godot/imported/left turn 90.fbx-7ede25625141e45b963a6f806ea7a4b6.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/left turn 90.fbx" +dest_files=["res://.godot/imported/left turn 90.fbx-7ede25625141e45b963a6f806ea7a4b6.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/left turn.fbx.import b/assets/Warrior+Animation/left turn.fbx.import new file mode 100644 index 0000000..9bce45c --- /dev/null +++ b/assets/Warrior+Animation/left turn.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cjs5o45dby6wq" +path="res://.godot/imported/left turn.fbx-c8dee54f4dff7f292b4eb1804655f5cb.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/left turn.fbx" +dest_files=["res://.godot/imported/left turn.fbx-c8dee54f4dff7f292b4eb1804655f5cb.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/right strafe walking.fbx.import b/assets/Warrior+Animation/right strafe walking.fbx.import new file mode 100644 index 0000000..ed357ff --- /dev/null +++ b/assets/Warrior+Animation/right strafe walking.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://drr52cg7ns8ak" +path="res://.godot/imported/right strafe walking.fbx-cc75d3596d7e2bd63ef1120c7d273ed9.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/right strafe walking.fbx" +dest_files=["res://.godot/imported/right strafe walking.fbx-cc75d3596d7e2bd63ef1120c7d273ed9.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/right strafe.fbx.import b/assets/Warrior+Animation/right strafe.fbx.import new file mode 100644 index 0000000..f9109ab --- /dev/null +++ b/assets/Warrior+Animation/right strafe.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://ctfgb4y033qak" +path="res://.godot/imported/right strafe.fbx-a432556f9ff1799cf9608c02c0ad131a.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/right strafe.fbx" +dest_files=["res://.godot/imported/right strafe.fbx-a432556f9ff1799cf9608c02c0ad131a.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/Warrior+Animation/right turn 90.fbx.import b/assets/Warrior+Animation/right turn 90.fbx.import new file mode 100644 index 0000000..ef7b291 --- /dev/null +++ b/assets/Warrior+Animation/right turn 90.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bfg20q58h3ifm" +path="res://.godot/imported/right turn 90.fbx-1510f429e9c72d07d6b8f5bd0c243b9d.scn" + +[deps] + +source_file="res://assets/Warrior+Animation/right turn 90.fbx" +dest_files=["res://.godot/imported/right turn 90.fbx-1510f429e9c72d07d6b8f5bd0c243b9d.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +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=true +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={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/assets/animations/Stop Walking.fbx.import b/assets/Warrior+Animation/right turn.fbx.import similarity index 76% rename from assets/animations/Stop Walking.fbx.import rename to assets/Warrior+Animation/right turn.fbx.import index be1dc26..8c406d8 100644 --- a/assets/animations/Stop Walking.fbx.import +++ b/assets/Warrior+Animation/right turn.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://dusjhgyamhmgt" -path="res://.godot/imported/Stop Walking.fbx-3d24a0466a0c2e2a6357ddcab01c8e7a.scn" +uid="uid://clk4yl4dln5dn" +path="res://.godot/imported/right turn.fbx-b101bc8730403d08b841e8c3a0b41769.scn" [deps] -source_file="res://assets/animations/Stop Walking.fbx" -dest_files=["res://.godot/imported/Stop Walking.fbx-3d24a0466a0c2e2a6357ddcab01c8e7a.scn"] +source_file="res://assets/Warrior+Animation/right turn.fbx" +dest_files=["res://.godot/imported/right turn.fbx-b101bc8730403d08b841e8c3a0b41769.scn"] [params] diff --git a/assets/animations/Walking.fbx.import b/assets/Warrior+Animation/running.fbx.import similarity index 77% rename from assets/animations/Walking.fbx.import rename to assets/Warrior+Animation/running.fbx.import index ccf2631..0c36de3 100644 --- a/assets/animations/Walking.fbx.import +++ b/assets/Warrior+Animation/running.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://bv1wgnjxtwty1" -path="res://.godot/imported/Walking.fbx-a17a5e9807a8458d066589326a10c768.scn" +uid="uid://cjbmdhkq1mvwf" +path="res://.godot/imported/running.fbx-ca8e5a7b9cad519a1f30381a5dfeccdf.scn" [deps] -source_file="res://assets/animations/Walking.fbx" -dest_files=["res://.godot/imported/Walking.fbx-a17a5e9807a8458d066589326a10c768.scn"] +source_file="res://assets/Warrior+Animation/running.fbx" +dest_files=["res://.godot/imported/running.fbx-ca8e5a7b9cad519a1f30381a5dfeccdf.scn"] [params] diff --git a/assets/animations/Jumping.fbx.import b/assets/Warrior+Animation/walking.fbx.import similarity index 77% rename from assets/animations/Jumping.fbx.import rename to assets/Warrior+Animation/walking.fbx.import index 4f1163f..7cbd1f0 100644 --- a/assets/animations/Jumping.fbx.import +++ b/assets/Warrior+Animation/walking.fbx.import @@ -3,13 +3,13 @@ importer="scene" importer_version=1 type="PackedScene" -uid="uid://kaq2rgjrxnm4" -path="res://.godot/imported/Jumping.fbx-785df5cde6fd6a2d61091c404e44b339.scn" +uid="uid://c547ty6nvf11c" +path="res://.godot/imported/walking.fbx-574a3fce7d862b76db3ed1e8c594f5b6.scn" [deps] -source_file="res://assets/animations/Jumping.fbx" -dest_files=["res://.godot/imported/Jumping.fbx-785df5cde6fd6a2d61091c404e44b339.scn"] +source_file="res://assets/Warrior+Animation/walking.fbx" +dest_files=["res://.godot/imported/walking.fbx-574a3fce7d862b76db3ed1e8c594f5b6.scn"] [params] diff --git a/assets/animations/Idle.fbx b/assets/animations/Idle.fbx deleted file mode 100644 index 9ae9979..0000000 Binary files a/assets/animations/Idle.fbx and /dev/null differ diff --git a/assets/animations/Jumping.fbx b/assets/animations/Jumping.fbx deleted file mode 100644 index f690e9c..0000000 Binary files a/assets/animations/Jumping.fbx and /dev/null differ diff --git a/assets/animations/Left Strafe Walking.fbx b/assets/animations/Left Strafe Walking.fbx deleted file mode 100644 index 012f3e0..0000000 Binary files a/assets/animations/Left Strafe Walking.fbx and /dev/null differ diff --git a/assets/animations/Right Strafe Walking.fbx b/assets/animations/Right Strafe Walking.fbx deleted file mode 100644 index 12fd98f..0000000 Binary files a/assets/animations/Right Strafe Walking.fbx and /dev/null differ diff --git a/assets/animations/Start Walking.fbx b/assets/animations/Start Walking.fbx deleted file mode 100644 index ddf3269..0000000 Binary files a/assets/animations/Start Walking.fbx and /dev/null differ diff --git a/assets/animations/Stop Walking.fbx b/assets/animations/Stop Walking.fbx deleted file mode 100644 index 2e5a804..0000000 Binary files a/assets/animations/Stop Walking.fbx and /dev/null differ diff --git a/assets/animations/Walking.fbx b/assets/animations/Walking.fbx deleted file mode 100644 index 28423cd..0000000 Binary files a/assets/animations/Walking.fbx and /dev/null differ diff --git a/assets/animations/Walking_2.png.import b/assets/animations/Walking_2.png.import deleted file mode 100644 index b138d32..0000000 --- a/assets/animations/Walking_2.png.import +++ /dev/null @@ -1,44 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dm2h4hkvqyoph" -path.s3tc="res://.godot/imported/Walking_2.png-ce8a6c3320aac98a8634c6e5cf2b9141.s3tc.ctex" -metadata={ -"imported_formats": ["s3tc_bptc"], -"vram_texture": true -} -generator_parameters={ -"md5": "16d2ed20714fd916290f48d68a233060" -} - -[deps] - -source_file="res://assets/animations/Walking_2.png" -dest_files=["res://.godot/imported/Walking_2.png-ce8a6c3320aac98a8634c6e5cf2b9141.s3tc.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=1 -compress/channel_pack=0 -mipmaps/generate=true -mipmaps/limit=-1 -roughness/mode=1 -roughness/src_normal="res://assets/animations/Walking_2.png" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/assets/models/warrior.fbx b/assets/models/warrior.fbx deleted file mode 100644 index c520735..0000000 Binary files a/assets/models/warrior.fbx and /dev/null differ diff --git a/assets/models/warrior_0.png b/assets/models/warrior_0.png deleted file mode 100644 index e90faaa..0000000 Binary files a/assets/models/warrior_0.png and /dev/null differ diff --git a/assets/models/warrior_0.png.import b/assets/models/warrior_0.png.import deleted file mode 100644 index a4caa45..0000000 --- a/assets/models/warrior_0.png.import +++ /dev/null @@ -1,44 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://kb2tcclrpsgp" -path.s3tc="res://.godot/imported/warrior_0.png-922555626e5523f977897d33a0262592.s3tc.ctex" -metadata={ -"imported_formats": ["s3tc_bptc"], -"vram_texture": true -} -generator_parameters={ -"md5": "a1dc5c32f83d0182b6af19d596014cce" -} - -[deps] - -source_file="res://assets/models/warrior_0.png" -dest_files=["res://.godot/imported/warrior_0.png-922555626e5523f977897d33a0262592.s3tc.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 diff --git a/assets/models/warrior_1.png b/assets/models/warrior_1.png deleted file mode 100644 index 5c2868b..0000000 Binary files a/assets/models/warrior_1.png and /dev/null differ diff --git a/assets/models/warrior_1.png.import b/assets/models/warrior_1.png.import deleted file mode 100644 index 10537b2..0000000 --- a/assets/models/warrior_1.png.import +++ /dev/null @@ -1,43 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dnlkuf4pjgbht" -path="res://.godot/imported/warrior_1.png-aafdc73fc8d6c19365ff4d442a16edcf.ctex" -metadata={ -"vram_texture": false -} -generator_parameters={ -"md5": "4201468097ebb1a15961ee25c2d64310" -} - -[deps] - -source_file="res://assets/models/warrior_1.png" -dest_files=["res://.godot/imported/warrior_1.png-aafdc73fc8d6c19365ff4d442a16edcf.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=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=1 diff --git a/assets/models/warrior_2.png b/assets/models/warrior_2.png deleted file mode 100644 index 85d82d1..0000000 Binary files a/assets/models/warrior_2.png and /dev/null differ diff --git a/camera_pivot.gd b/camera_pivot.gd index 344e374..3f8847d 100644 --- a/camera_pivot.gd +++ b/camera_pivot.gd @@ -1,32 +1,76 @@ # CameraPivot.gd -# Steuert die Third-Person-Kamera: Maussteuerung (RMB), Zoom per Mausrad +# ───────────────────────────────────────────────────────────────────────────── +# Third-Person Kamerasystem – Kind-Node des Spielers +# +# Kamera-Modi: +# • Kein Ziel, LMB gehalten → Kamera orbitet um den Spieler (world_yaw ändert sich, +# Spielerrotation bleibt) +# • Kein Ziel, RMB gehalten → Spieler + Kamera drehen sich gemeinsam +# • Ziel markiert → Soft Lock-On: Spieler dreht sich smooth zum Ziel, +# Kamera bleibt direkt dahinter +# • Mausrad → Zoom (min_zoom … max_zoom) +# +# world_yaw: absolute Weltausrichtung der Kamera in Grad (Y-Achse). +# Unabhängig von player.rotation.y → verhindert Feedback-Loop bei Souls-Rotation. +# camera_pivot.rotation.y wird in _process() immer als (world_yaw - player.rotation.y) +# gesetzt, sodass die Kamera in World-Space stabil bleibt. +# ───────────────────────────────────────────────────────────────────────────── extends Node3D -@export var sensitivity = 0.3 # Mausempfindlichkeit -@export var min_pitch = -40.0 # Maximale Neigung nach unten -@export var max_pitch = 20.0 # Maximale Neigung nach oben -@export var min_zoom = 5.0 # Minimale Kameraentfernung -@export var max_zoom = 20.0 # Maximale Kameraentfernung -@export var zoom_speed = 1.0 # Zoom-Geschwindigkeit pro Mausrad-Schritt +@export var sensitivity = 0.3 +@export var min_pitch = -40.0 +@export var max_pitch = 20.0 +@export var min_zoom = 5.0 +@export var max_zoom = 20.0 +@export var zoom_speed = 1.0 +@export var lock_on_speed = 5.0 -var pitch = 0.0 +var pitch: float = 0.0 +var world_yaw: float = 0.0 # Absolute Weltausrichtung der Kamera (unabhängig von Spielerrotation) @onready var camera = $Camera3D func _ready(): Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + world_yaw = get_parent().rotation.y func _input(event): - # RMB gehalten: Kamera drehen - if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): - get_parent().rotate_y(deg_to_rad(-event.relative.x * sensitivity)) - pitch -= event.relative.y * sensitivity - pitch = clamp(pitch, min_pitch, max_pitch) - rotation_degrees.x = pitch + var player = get_parent() + var has_target = player.target != null and is_instance_valid(player.target) + + if event is InputEventMouseMotion: + if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and not has_target: + # LMB: nur Kamera dreht sich, Spieler bleibt + world_yaw -= deg_to_rad(event.relative.x * sensitivity) + pitch -= event.relative.y * sensitivity + pitch = clamp(pitch, min_pitch, max_pitch) + rotation_degrees.x = pitch + elif Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) and not has_target: + # RMB: Spieler + Kamera drehen sich gemeinsam + var delta_yaw = deg_to_rad(-event.relative.x * sensitivity) + world_yaw += delta_yaw + player.rotation.y += delta_yaw + pitch -= event.relative.y * sensitivity + pitch = clamp(pitch, min_pitch, max_pitch) + rotation_degrees.x = pitch - # Mausrad: Zoom if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: camera.position.z = clamp(camera.position.z + zoom_speed, min_zoom, max_zoom) if event.button_index == MOUSE_BUTTON_WHEEL_UP: camera.position.z = clamp(camera.position.z - zoom_speed, min_zoom, max_zoom) + +func _process(delta): + var player = get_parent() + + if player.target != null and is_instance_valid(player.target) and not player.is_rolling: + # Soft Lock-On: Spieler dreht sich zum Ziel, Kamera folgt direkt dahinter + var to_target = player.target.global_position - player.global_position + to_target.y = 0 + if to_target.length() > 0.1: + var target_angle = atan2(-to_target.x, -to_target.z) + player.rotation.y = lerp_angle(player.rotation.y, target_angle, delta * lock_on_speed) + world_yaw = player.rotation.y + + # Lokale Rotation so setzen dass Kamera immer auf world_yaw zeigt + rotation.y = world_yaw - player.rotation.y diff --git a/enemy.gd b/enemy.gd index 72f9a5c..66d02fd 100644 --- a/enemy.gd +++ b/enemy.gd @@ -1,319 +1,205 @@ # Enemy.gd -# Steuert den Gegner: KI-Bewegung zum Spieler, Angriff, HP, Zielanzeige +# ───────────────────────────────────────────────────────────────────────────── +# Gegner-KI – State Machine mit NavigationAgent3D +# +# Zustände: +# IDLE → wartet bis Spieler in detection_range kommt +# CHASING → läuft via NavMesh zum Spieler +# ATTACKING → steht, dreht sich zum Spieler, greift in attack_speed-Intervallen an +# DEAD → Kollision deaktiviert, Node wird nach kurzer Verzögerung entfernt +# +# Signale: +# enemy_died(spawn_position, xp_reward) → World.gd → Respawn + XP +# enemy_dropped_loot(loot, world_pos) → World.gd → Spieler → LootWindow +# ───────────────────────────────────────────────────────────────────────────── extends CharacterBody3D +# ═══════════════════════════════════════════════════════════════ +# SIGNALE +# ═══════════════════════════════════════════════════════════════ + signal enemy_died(spawn_position: Vector3, xp_reward: int) -signal enemy_dropped_loot(loot: Dictionary, world_position: Vector3) +signal enemy_dropped_loot(loot: Dictionary, world_pos: Vector3) + +# ═══════════════════════════════════════════════════════════════ +# STATS +# ═══════════════════════════════════════════════════════════════ + +@export var max_hp: int = 50 +@export var min_damage: int = 3 +@export var max_damage: int = 7 +@export var attack_range: float = 2.0 +@export var attack_speed: float = 2.0 # Sekunden zwischen Angriffen +@export var move_speed: float = 3.0 +@export var xp_reward: int = 20 +@export var detection_range: float = 15.0 +@export var loot_table: LootTable = null + +var current_hp: int +var target = null # Spieler + +# ═══════════════════════════════════════════════════════════════ +# ZUSTAND +# ═══════════════════════════════════════════════════════════════ + +enum State { IDLE, CHASING, ATTACKING, DEAD } +var state: State = State.IDLE + +var attack_cooldown: float = 0.0 +var is_dead: bool = false -const SPEED = 3.0 -const PATROL_SPEED = 1.5 const GRAVITY = 9.8 -const ATTACK_RANGE = 1.5 -const ATTACK_COOLDOWN = 2.0 -const AGGRO_RANGE = 8.0 # Entfernung ab der der Gegner angreift -const PATROL_RADIUS = 5.0 # Radius um Spawn-Position für Patrol -const PATROL_WAIT_TIME = 2.0 # Wartezeit am Patrol-Punkt -# Level-Differenz Konstanten -const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz -const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation +# ═══════════════════════════════════════════════════════════════ +# NODE-REFERENZEN +# ═══════════════════════════════════════════════════════════════ -enum State { PATROL, CHASE, ATTACK } +@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D +@onready var health_label: Label3D = $HealthDisplay/Label3D -# Stats-System -@export var level: int = 1 -@export var base_strength: int = 8 -@export var base_stamina: int = 10 -@export var base_armor: int = 5 # Rüstung reduziert Nahkampfschaden - -# Berechnete Stats -var strength: int = 8 -var stamina: int = 10 -var armor: int = 5 -var max_hp: int = 100 -var current_hp: int = 100 -var attack_damage: int = 5 - -# XP-Belohnung (skaliert mit Level) -var xp_reward: int = 25 - -# Loot-System -@export var loot_table: LootTable - -var target = null # Spieler-Referenz (wird von World gesetzt) -var can_attack = true -var spawn_position: Vector3 # Ursprüngliche Spawn-Position -var current_state = State.PATROL -var patrol_target: Vector3 # Aktuelles Patrol-Ziel -var is_waiting = false # Ob Gegner am Patrol-Punkt wartet - -# Animation System -const ANIMATION_FILES = { - "walk": "res://assets/animations/Walking.fbx", - "autoattack": "res://assets/animations/Autoattack.fbx", - "die": "res://assets/animations/Dying Backwards.fbx", - "idle": "res://assets/animations/Idle.fbx", -} -const LOOP_ANIMATIONS = ["walk", "idle"] -var anim_player: AnimationPlayer = null -var current_anim: String = "" - -@onready var health_label = $HealthLabel +# ═══════════════════════════════════════════════════════════════ +# READY +# ═══════════════════════════════════════════════════════════════ func _ready(): - _calculate_stats() current_hp = max_hp - health_label.visible = false - _update_label() - spawn_position = global_position - _pick_new_patrol_target() - _setup_animations() - -# Animationen laden -func _setup_animations(): - var model = get_node_or_null("EnemyModel") - if model == null: - return - anim_player = _find_node_by_class(model, "AnimationPlayer") - if anim_player == null: - anim_player = AnimationPlayer.new() - anim_player.name = "AnimationPlayer" - model.add_child(anim_player) - var lib: AnimationLibrary - if anim_player.has_animation_library(""): - lib = anim_player.get_animation_library("") - else: - lib = AnimationLibrary.new() - anim_player.add_animation_library("", lib) - for anim_id in ANIMATION_FILES: - var scene = load(ANIMATION_FILES[anim_id]) as PackedScene - if scene == null: - continue - var instance = scene.instantiate() - var source_ap = _find_node_by_class(instance, "AnimationPlayer") - if source_ap: - var names = source_ap.get_animation_list() - if names.size() > 0: - var anim = source_ap.get_animation(names[0]) - if anim_id in ["walk", "idle"]: - anim.loop_mode = Animation.LOOP_LINEAR - if lib.has_animation(anim_id): - lib.remove_animation(anim_id) - lib.add_animation(anim_id, anim) - instance.queue_free() - -func _find_node_by_class(node: Node, class_name_str: String) -> Node: - for child in node.get_children(): - if child.get_class() == class_name_str: - return child - var result = _find_node_by_class(child, class_name_str) - if result: - return result - return null - -func _play_anim(anim_name: String): - if anim_player == null: - return - # Wenn Animation ausgelaufen ist, zurücksetzen - if not anim_player.is_playing(): - current_anim = "" - if anim_name != current_anim: - current_anim = anim_name - if anim_player.has_animation(anim_name): - anim_player.play(anim_name) - else: - anim_player.stop() - -# Stats basierend auf Level berechnen -func _calculate_stats(): - var levels_gained = level - 1 - strength = base_strength + levels_gained * 2 - stamina = base_stamina + levels_gained * 3 - armor = base_armor + levels_gained * 2 - - # HP = Stamina * 10 - max_hp = stamina * 10 - # Schaden = Stärke / 2 - attack_damage = int(strength * 0.5) + 2 - # XP = 25 * Level - xp_reward = 25 * level - - print("Enemy Stats (Lv", level, ") - STR:", strength, " STA:", stamina, " ARM:", armor, " HP:", max_hp, " DMG:", attack_damage) - -# Schaden mit Rüstung und Level-Differenz berechnen -func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int: - var damage = float(raw_damage) - - # Rüstung reduziert nur Nahkampfschaden - if is_melee: - # Rüstungsreduktion: armor / (armor + 50) = Prozent Reduktion - # Bei 5 Rüstung: 5/55 = ~9% Reduktion - # Bei 20 Rüstung: 20/70 = ~29% Reduktion - var armor_reduction = float(armor) / (float(armor) + 50.0) - damage = damage * (1.0 - armor_reduction) - - # Level-Differenz Modifikator - var level_diff = attacker_level - level - var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD) - damage = damage * (1.0 + level_mod) - - return maxi(1, int(damage)) # Mindestens 1 Schaden - -# HP-Label Text aktualisieren -func _update_label(): - health_label.text = "Lv" + str(level) + " " + str(current_hp) + "/" + str(max_hp) - -# HP-Label anzeigen (wenn Gegner markiert wird) -func show_health(): - health_label.visible = true - -# HP-Label verstecken (wenn Markierung aufgehoben wird) -func hide_health(): + _update_health_display() health_label.visible = false -# Schaden nehmen und Label aktualisieren -func take_damage(amount): - current_hp -= amount - _update_label() - # Aggro bei Schaden — sofort angreifen - if current_state == State.PATROL: - current_state = State.CHASE - is_waiting = false - print("Gegner wurde angegriffen und verfolgt den Spieler!") - if current_hp <= 0: - die() + # NavigationAgent konfigurieren + nav_agent.path_desired_distance = 0.5 + nav_agent.target_desired_distance = attack_range * 0.9 -# Schaden mit vollem Schadenssystem (Rüstung, Level-Differenz) -func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true): - var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee) - print("Eingehender Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)") - take_damage(final_damage) - -# Gegner aus der Szene entfernen -func die(): - print("Gegner besiegt! +", xp_reward, " XP") - # XP an Spieler geben - if target and target.has_method("gain_xp"): - target.gain_xp(xp_reward) - # Loot generieren und droppen - _drop_loot() - enemy_died.emit(spawn_position, xp_reward) - # Death-Animation abspielen, dann entfernen - if anim_player and anim_player.has_animation("die"): - _play_anim("die") - # Kollision deaktivieren damit der Gegner nicht mehr im Weg ist - set_physics_process(false) - $CollisionShape3D.set_deferred("disabled", true) - await anim_player.animation_finished - queue_free() - -# Loot generieren basierend auf LootTable -func _drop_loot(): - if loot_table == null: - # Standard-Gold-Drop wenn keine LootTable zugewiesen - var gold = randi_range(1, 3) * level - var loot = {"gold": gold, "items": []} - enemy_dropped_loot.emit(loot, global_position) - return - - var loot = loot_table.generate_loot() - # Gold mit Level skalieren - loot["gold"] = loot["gold"] * level - enemy_dropped_loot.emit(loot, global_position) +# ═══════════════════════════════════════════════════════════════ +# PHYSICS PROCESS +# ═══════════════════════════════════════════════════════════════ func _physics_process(delta): + if is_dead: + return + + # Schwerkraft if not is_on_floor(): velocity.y -= GRAVITY * delta - if target == null: - # Ohne Spieler-Referenz nur patrouillieren - _do_patrol() + # Cooldown herunterzählen + if attack_cooldown > 0: + attack_cooldown -= delta + + # Kein Ziel → Idle + if target == null or not is_instance_valid(target): + state = State.IDLE + velocity.x = 0 + velocity.z = 0 move_and_slide() return - # Prüfe Distanz zum Spieler für Aggro - var distance_to_player = global_position.distance_to(target.global_position) + var distance = global_position.distance_to(target.global_position) - # State-Wechsel basierend auf Distanz - match current_state: - State.PATROL: - if distance_to_player <= AGGRO_RANGE: - current_state = State.CHASE - print("Gegner hat Spieler entdeckt!") + match state: + State.IDLE: + if distance <= detection_range: + state = State.CHASING + State.CHASING: + if distance <= attack_range: + state = State.ATTACKING + velocity.x = 0 + velocity.z = 0 else: - _do_patrol() - State.CHASE: - if distance_to_player <= ATTACK_RANGE: - current_state = State.ATTACK - else: - _chase_player() - State.ATTACK: - if distance_to_player > ATTACK_RANGE: - current_state = State.CHASE + _move_toward_target() + State.ATTACKING: + if distance > attack_range * 1.5: + state = State.CHASING else: velocity.x = 0 velocity.z = 0 - _play_anim("idle") # Idle beim Angriff - if can_attack: - _attack() + _face_target() + if attack_cooldown <= 0: + _perform_attack() move_and_slide() -# Neues Patrol-Ziel in der Nähe der Spawn-Position wählen -func _pick_new_patrol_target(): - var angle = randf() * TAU # Zufälliger Winkel - var distance = randf_range(2.0, PATROL_RADIUS) - patrol_target = spawn_position + Vector3(cos(angle) * distance, 0, sin(angle) * distance) - -# Patrol-Verhalten: Zufällig herumlaufen -func _do_patrol(): - if is_waiting: +func _move_toward_target(): + if target == null: return - - var distance_to_patrol = global_position.distance_to(patrol_target) - - if distance_to_patrol <= 0.5: - # Am Ziel angekommen, warten und neues Ziel wählen - velocity.x = 0 - velocity.z = 0 - _play_anim("idle") - _wait_at_patrol_point() - else: - # Zum Patrol-Ziel laufen - var direction = (patrol_target - global_position) - direction.y = 0 - direction = direction.normalized() - velocity.x = direction.x * PATROL_SPEED - velocity.z = direction.z * PATROL_SPEED - _play_anim("walk") - look_at(Vector3(patrol_target.x, global_position.y, patrol_target.z)) - -# Am Patrol-Punkt warten -func _wait_at_patrol_point(): - is_waiting = true - await get_tree().create_timer(PATROL_WAIT_TIME).timeout - is_waiting = false - _pick_new_patrol_target() - -# Spieler verfolgen -func _chase_player(): - _play_anim("walk") - var direction = (target.global_position - global_position) + nav_agent.target_position = target.global_position + if nav_agent.is_navigation_finished(): + return + var next_pos = nav_agent.get_next_path_position() + var direction = (next_pos - global_position).normalized() direction.y = 0 - direction = direction.normalized() - velocity.x = direction.x * SPEED - velocity.z = direction.z * SPEED - look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z)) + velocity.x = direction.x * move_speed + velocity.z = direction.z * move_speed + _face_direction(direction) -# Angriff mit Cooldown -func _attack(): - can_attack = false - _play_anim("autoattack") - # Gegner verwendet auch das Schadenssystem mit Level-Differenz - if target.has_method("take_damage_from"): - target.take_damage_from(attack_damage, level, true) - else: - target.take_damage(attack_damage) - print("Gegner (Lv", level, ") greift an: ", attack_damage, " Schaden") - await get_tree().create_timer(ATTACK_COOLDOWN).timeout - can_attack = true +func _face_target(): + if target == null: + return + var dir = (target.global_position - global_position) + dir.y = 0 + if dir.length() > 0.01: + _face_direction(dir.normalized()) + +func _face_direction(dir: Vector3): + if dir.length() > 0.01: + rotation.y = atan2(dir.x, dir.z) + +# ═══════════════════════════════════════════════════════════════ +# KAMPF +# ═══════════════════════════════════════════════════════════════ + +func _perform_attack(): + if target == null or not is_instance_valid(target): + return + var damage = randi_range(min_damage, max_damage) + target.take_damage(damage) + attack_cooldown = attack_speed + print(name + " greift an: " + str(damage) + " Schaden") + +func take_damage(amount: int): + if is_dead: + return + current_hp = clamp(current_hp - amount, 0, max_hp) + _update_health_display() + if current_hp <= 0: + _die() + +# ═══════════════════════════════════════════════════════════════ +# HP-ANZEIGE +# ═══════════════════════════════════════════════════════════════ + +func show_health(): + if health_label: + health_label.visible = true + +func hide_health(): + if health_label: + health_label.visible = false + +func _update_health_display(): + if health_label: + health_label.text = str(current_hp) + " / " + str(max_hp) + +# ═══════════════════════════════════════════════════════════════ +# TOD & LOOT +# ═══════════════════════════════════════════════════════════════ + +func _die(): + is_dead = true + state = State.DEAD + velocity = Vector3.ZERO + print(name + " gestorben!") + + # Loot generieren + if loot_table != null: + var loot = loot_table.generate_loot() + enemy_dropped_loot.emit(loot, global_position) + + # XP und Respawn-Signal + enemy_died.emit(global_position, xp_reward) + + # Kollision deaktivieren und Node entfernen + set_deferred("collision_layer", 0) + set_deferred("collision_mask", 0) + await get_tree().create_timer(1.5).timeout + queue_free() diff --git a/enemy.gd.uid b/enemy.gd.uid index 963bf6f..368bde8 100644 --- a/enemy.gd.uid +++ b/enemy.gd.uid @@ -1 +1 @@ -uid://bg5qs3pcfp7p7 +uid://gaqwoakxyhet diff --git a/enemy.tscn b/enemy.tscn index cb51e5a..5c3c3b2 100644 --- a/enemy.tscn +++ b/enemy.tscn @@ -1,36 +1,26 @@ [gd_scene format=3 uid="uid://cvojaeanxugfj"] -[ext_resource type="PackedScene" uid="uid://da1w523lg7i2b" path="res://assets/models/warrior.fbx" id="1_7k104"] -[ext_resource type="Script" uid="uid://bg5qs3pcfp7p7" path="res://enemy.gd" id="2_enemy"] +[ext_resource type="Script" path="res://enemy.gd" id="1_enemy"] -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4gyqm"] -radius = 0.6 -height = 3.0 +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] +radius = 0.4 +height = 1.8 -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7k104"] -radius = 0.6 -height = 3.0 +[node name="Enemy" type="CharacterBody3D"] +script = ExtResource("1_enemy") -[node name="Enemy" type="CharacterBody3D" unique_id=332011146] -script = ExtResource("2_enemy") +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0) +shape = SubResource("CapsuleShape3D_1") -[node name="EnemyModel" parent="." unique_id=846574684 instance=ExtResource("1_7k104")] -transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0) +[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] -[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1323028920] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0) -shape = SubResource("CapsuleShape3D_4gyqm") +[node name="HealthDisplay" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0) -[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=457987844] - -[node name="Area3D" type="Area3D" parent="." unique_id=1689838821] - -[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D" unique_id=116643275] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0) -shape = SubResource("CapsuleShape3D_7k104") - -[node name="HealthLabel" type="Label3D" parent="." unique_id=1251847350] -transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 3.5, 0) -visible = false +[node name="Label3D" type="Label3D" parent="HealthDisplay"] +pixel_size = 0.01 +billboard = 3 text = "50 / 50" +font_size = 64 outline_size = 8 diff --git a/player.gd b/player.gd index ed457bf..444edc9 100644 --- a/player.gd +++ b/player.gd @@ -1,928 +1,697 @@ # Player.gd -# Steuert den Spielercharakter: Bewegung, Kamera, HP, Angriff, Zielauswahl +# ───────────────────────────────────────────────────────────────────────────── +# Hauptskript des Spielers – RPG/Souls-Hybrid +# +# Bewegungssystem: +# • Kein Ziel + kein Walk → Souls-Modus: Charakter dreht sich zur Laufrichtung +# (relativ zu camera_pivot.world_yaw), Animation = "run" +# RMB gehalten: A/D = Strafe statt Drehung +# • Kein Ziel + Walk (NumLock) → RPG-Modus: Strafe-Animationen, langsame Geschwindigkeit +# • Ziel markiert → Lock-On: Kamera dreht Spieler zum Ziel, +# WASD = Strafe/Rückwärts relativ zur Spielerausrichtung +# +# Kamera-Referenz: +# dir3 (Bewegungsvektor) nutzt camera_pivot.world_yaw im Souls-Modus, damit sich +# der Vektor NICHT mit der Spielerrotation dreht (kein Feedback-Loop). +# +# Kampf: +# Autoattack, Heavy Strike (Krieger), Quick Strike (Schurke), Frostbolt (Magier) +# Global Cooldown, Skill Cooldowns, Cast-System +# +# Animationen: +# Werden zur Laufzeit aus FBX-Dateien geladen (_load_anim_from_fbx). +# Root Motion wird automatisch entfernt (_strip_root_motion). +# ───────────────────────────────────────────────────────────────────────────── extends CharacterBody3D +# ═══════════════════════════════════════════════════════════════ +# KONSTANTEN +# ═══════════════════════════════════════════════════════════════ + const SPEED = 5.0 +const SPRINT_SPEED = 9.0 const JUMP_VELOCITY = 4.5 const GRAVITY = 9.8 -# Charakter-Klasse und Level-System -@export var character_class: CharacterClass +# Scenes für UI-Panels (werden zur Laufzeit erstellt) +const LOOT_WINDOW_SCENE = preload("res://loot_window.tscn") +const INVENTORY_PANEL_SCENE = preload("res://inventory_panel.tscn") +const SKILL_PANEL_SCENE = preload("res://skill_panel.tscn") +const CHARACTER_PANEL_SCENE = preload("res://character_panel.tscn") + +# Animations-FBX Pfade +const ANIM_IDLE = "res://assets/Warrior+Animation/idle.fbx" +const ANIM_WALK = "res://assets/Warrior+Animation/walking.fbx" +const ANIM_RUN = "res://assets/Warrior+Animation/running.fbx" +const ANIM_JUMP = "res://assets/Warrior+Animation/jump.fbx" +const ANIM_AUTOATTACK = "res://assets/Warrior+Animation/Autoattack.fbx" +const ANIM_HEAVY_STRIKE = "res://assets/Warrior+Animation/Heavy Strike.fbx" +const ANIM_DEATH = "res://assets/Warrior+Animation/Dying Backwards.fbx" +const ANIM_WALK_BACK = "res://assets/Warrior+Animation/Walking Backwards.fbx" +const ANIM_STRAFE_LEFT = "res://assets/Warrior+Animation/left strafe walking.fbx" +const ANIM_STRAFE_RIGHT = "res://assets/Warrior+Animation/right strafe walking.fbx" +const ANIM_RUN_STRAFE_LEFT = "res://assets/Warrior+Animation/Running Left Strafe.fbx" +const ANIM_RUN_STRAFE_RIGHT = "res://assets/Warrior+Animation/Running Right Strafe run.fbx" +const ANIM_WALK_JUMP = "res://assets/Warrior+Animation/Walking Jump.fbx" +const ANIM_RUN_JUMP = "res://assets/Warrior+Animation/Running Jump.fbx" +const ANIM_ROLL = "res://assets/Warrior+Animation/Quick Roll To Run.fbx" +const ANIM_TURN_180 = "res://assets/Warrior+Animation/Running Turn 180.fbx" + +# ═══════════════════════════════════════════════════════════════ +# CHARAKTER-STATS +# ═══════════════════════════════════════════════════════════════ + +var character_class: CharacterClass = null + var level: int = 1 var current_xp: int = 0 -var xp_to_next_level: int = 100 # XP benötigt für Level 2 +var xp_to_next_level: int = 100 -# Aktuelle Stats (berechnet aus Klasse + Level) +# Basis-Stats (aus Klasse + Items berechnet) var strength: int = 10 var agility: int = 10 var intelligence: int = 10 var stamina: int = 10 -var armor: int = 0 # Rüstung aus Ausrüstung +var armor: int = 0 -# Level-Differenz Konstanten -const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz -const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation +# HP und Ressource +var max_hp: int = 100 +var current_hp: int = 100 +var max_resource: int = 0 +var current_resource: int = 0 -var max_hp = 100 -var current_hp = 100 -var max_resource = 0 # Klassen-Ressource (Mana/Energie/Wut), 0 = keine -var current_resource = 0 -var target = null # Aktuell markierter Gegner +# Mana-Aliases (für Consumable-Kompatibilität) +var max_mana: int: + get: return max_resource +var current_mana: int: + get: return current_resource -# Aktionsleiste: Skills (String) oder Consumables in Slots (0-8) -var action_bar_items: Array = [null, null, null, null, null, null, null, null, null] +# ═══════════════════════════════════════════════════════════════ +# EQUIPMENT +# ═══════════════════════════════════════════════════════════════ -# Alle verfügbaren Skills (für Fähigkeiten-Panel) — wird klassenabhängig befüllt -var available_skills: Array = [] - -# Skill-Definitionen pro Klasse -const AUTOATTACK_SKILL = {"id": "autoattack", "name": "Autoattack", "icon": "res://icons/autoattack_icon.svg", "description": "Greift das Ziel im Nahkampf an.\nSchaden: Waffenschaden + Main-Stat"} -const MELEE_SKILLS = [ - {"id": "heavy_strike", "name": "Heavy Strike", "icon": "res://icons/heavy_strike_icon.svg", "description": "Starker Hieb mit 3s Cooldown.\nSchaden: 10-15 + Main-Stat\nReichweite: 4.0"}, -] -const MAGE_SKILLS = [ - {"id": "wand", "name": "Zauberstab", "icon": "res://icons/wand_icon.svg", "description": "Magischer Fernkampfangriff.\nSchaden: Waffenschaden + INT\nReichweite: 20.0\nIgnoriert Rüstung\nDeaktiviert Autoattack"}, - {"id": "frostbolt", "name": "Frostblitz", "icon": "res://icons/frostbolt_icon.svg", "description": "Magischer Fernkampfangriff mit Castzeit.\nSchaden: 12-20 + INT\nManakosten: 20\nReichweite: 20.0\nCastzeit: 1.5s\nCooldown: 2.5s"}, -] -var potion_cooldown: float = 0.0 -const POTION_COOLDOWN_TIME = 1.0 - -# Equipment System var equipment: Dictionary = { - Equipment.Slot.HEAD: null, - Equipment.Slot.CHEST: null, - Equipment.Slot.HANDS: null, - Equipment.Slot.LEGS: null, - Equipment.Slot.FEET: null, - Equipment.Slot.WEAPON: null, - Equipment.Slot.OFFHAND: null + Equipment.Slot.HEAD: null, + Equipment.Slot.CHEST: null, + Equipment.Slot.HANDS: null, + Equipment.Slot.LEGS: null, + Equipment.Slot.FEET: null, + Equipment.Slot.WEAPON: null, + Equipment.Slot.OFFHAND: null, } -# Inventar System +# ═══════════════════════════════════════════════════════════════ +# INVENTAR +# ═══════════════════════════════════════════════════════════════ + var inventory: Inventory = Inventory.new() -# Global Cooldown System (GCD) - gilt für alle Aktionen inkl. Autoattack -var global_cooldown = 0.0 -const BASE_GCD = 1.5 # Basis-GCD in Sekunden (wird durch Haste modifiziert) -var haste: float = 0.0 # Angriffsgeschwindigkeits-Bonus (0.1 = 10% schneller) +# ═══════════════════════════════════════════════════════════════ +# SKILL & AKTIONSLEISTE +# ═══════════════════════════════════════════════════════════════ -# Autoattack System -var autoattack_active = false # Ob Autoattack aktiv ist +var available_skills: Array = [] # Alle verfügbaren Skills der Klasse +var action_bar: Array = [] # 9 Slots: skill_id (String) oder Consumable oder null +var skill_cooldowns: Dictionary = {} # skill_id -> verbleibende Zeit +var consumable_cooldowns: Dictionary = {} # item_name -> verbleibende Zeit -# Zauberstab System (Magier-Fernkampf, exklusiv mit Autoattack) -var wand_active = false -const WAND_RANGE = 20.0 +# Global Cooldown +var global_cooldown: float = 0.0 +const GLOBAL_COOLDOWN_TIME: float = 1.5 -# Skills System - individuelle Cooldowns (zusätzlich zum GCD) -var heavy_strike_cooldown = 0.0 -const HEAVY_STRIKE_DAMAGE_MIN = 10 -const HEAVY_STRIKE_DAMAGE_MAX = 15 -const HEAVY_STRIKE_COOLDOWN = 3.0 -const HEAVY_STRIKE_RANGE = 4.0 +# Autoattack +var autoattack_active: bool = false +const HEAVY_STRIKE_COOLDOWN: float = 3.0 +const HEAVY_STRIKE_RANGE: float = 2.0 -var frostbolt_cooldown = 0.0 -const FROSTBOLT_DAMAGE_MIN = 12 -const FROSTBOLT_DAMAGE_MAX = 20 -const FROSTBOLT_COOLDOWN = 2.5 -const FROSTBOLT_RANGE = 20.0 -const FROSTBOLT_MANA_COST = 20 -const FROSTBOLT_CAST_TIME = 1.5 +# Cast System +var is_casting: bool = false +var cast_time_remaining: float = 0.0 +var cast_total: float = 0.0 +var pending_skill_id: String = "" -# Cast-System -var is_casting = false -var cast_time_remaining = 0.0 -var cast_time_total = 0.0 -var cast_spell_id = "" # Welcher Zauber gecastet wird +# ═══════════════════════════════════════════════════════════════ +# KAMPF +# ═══════════════════════════════════════════════════════════════ + +var target = null # Aktuell markierter Gegner + +# ═══════════════════════════════════════════════════════════════ +# ANIMATION +# ═══════════════════════════════════════════════════════════════ -# Animation System -const ANIMATION_FILES = { - "start_walk": "res://assets/animations/Start Walking.fbx", - "walk": "res://assets/animations/Walking.fbx", - "stop_walk": "res://assets/animations/Stop Walking.fbx", - "walk_back": "res://assets/animations/Walking Backwards.fbx", - "strafe_left": "res://assets/animations/Left Strafe Walking.fbx", - "strafe_right": "res://assets/animations/Right Strafe Walking.fbx", - "jump": "res://assets/animations/Jumping.fbx", - "autoattack": "res://assets/animations/Autoattack.fbx", - "heavy_strike": "res://assets/animations/Heavy Strike.fbx", - "die": "res://assets/animations/Dying Backwards.fbx", - "idle": "res://assets/animations/Idle.fbx", -} -# Animations-State für Walk-Kette -var walk_state: String = "" # "", "start", "walking", "stop" var anim_player: AnimationPlayer = null -var current_anim: String = "" +var is_attacking: bool = false +var is_dead: bool = false +var is_walking: bool = false +var is_rolling: bool = false +var roll_direction: Vector3 = Vector3.ZERO +var roll_cooldown: float = 0.0 +const ROLL_COOLDOWN: float = 1.5 + +# ═══════════════════════════════════════════════════════════════ +# NODE-REFERENZEN +# ═══════════════════════════════════════════════════════════════ @onready var camera_pivot = $CameraPivot @onready var camera = $CameraPivot/Camera3D @onready var hud = $HUD -@onready var character_panel = $CharacterPanel -@onready var inventory_panel = $InventoryPanel -@onready var loot_window = $LootWindow -@onready var skill_panel = $SkillPanel +@onready var model = $Model + +# UI-Panels (zur Laufzeit erstellt) +var loot_window = null +var inventory_panel = null +var skill_panel = null +var character_panel = null + +# ═══════════════════════════════════════════════════════════════ +# READY +# ═══════════════════════════════════════════════════════════════ func _ready(): - # Stats aus Klasse berechnen - _calculate_stats() - current_hp = max_hp - current_resource = max_resource + # Jolt Physics: Boden sicher erkennen + floor_snap_length = 0.3 + floor_max_angle = deg_to_rad(50.0) - hud.update_health(current_hp, max_hp) - hud.update_resource(current_resource, max_resource, get_resource_name()) - hud.update_level(level, current_xp, xp_to_next_level) - hud.set_active_slot(0) - # Skills klassenabhängig aufbauen - _init_class_skills() - # Aktionsleiste initialisieren (Skills + Items) + # Aktionsleiste mit 9 leeren Slots initialisieren + action_bar.resize(9) for i in range(9): - _refresh_action_slot(i) + action_bar[i] = null - # HUD-Klicks und Drag verbinden + # Animationen einrichten + _setup_animations() + + # UI-Panels erstellen + _create_ui_panels() + + # HUD Signale verbinden hud.slot_clicked.connect(_on_slot_clicked) hud.slot_drag_removed.connect(_on_slot_drag_removed) hud.slot_drag_swapped.connect(_on_slot_drag_swapped) - # Inventar Panel initialisieren - inventory_panel.setup(self) + hud.update_health(current_hp, max_hp) - # Loot Window initialisieren +# ═══════════════════════════════════════════════════════════════ +# UI-PANELS +# ═══════════════════════════════════════════════════════════════ + +func _create_ui_panels(): + loot_window = LOOT_WINDOW_SCENE.instantiate() + add_child(loot_window) loot_window.setup(self) - # Skill Panel initialisieren + inventory_panel = INVENTORY_PANEL_SCENE.instantiate() + add_child(inventory_panel) + inventory_panel.setup(self) + + skill_panel = SKILL_PANEL_SCENE.instantiate() + add_child(skill_panel) skill_panel.setup(self) - # Gold im HUD aktualisieren wenn sich Gold ändert + character_panel = CHARACTER_PANEL_SCENE.instantiate() + add_child(character_panel) + + # Gold-Änderungen an HUD weiterleiten inventory.gold_changed.connect(func(amount): hud.update_gold(amount)) - # Animationen laden - _setup_animations() +# ═══════════════════════════════════════════════════════════════ +# KLASSEN-INITIALISIERUNG +# ═══════════════════════════════════════════════════════════════ -# Animationen aus FBX-Dateien laden und in AnimationPlayer einbinden -func _setup_animations(): - var model = get_node_or_null("PlayerModel") - if model == null: +func _init_class_skills(): + available_skills.clear() + + # Autoattack ist für alle Klassen verfügbar + available_skills.append({ + "id": "autoattack", + "name": "Autoattack", + "description": "Grundangriff mit ausgerüsteter Waffe.", + "icon": "res://icons/autoattack_icon.svg", + "cooldown": 0.0, + "cast_time": 0.0, + }) + + if character_class == null: return - # AnimationPlayer im Modell finden - anim_player = _find_node_by_class(model, "AnimationPlayer") - if anim_player == null: - anim_player = AnimationPlayer.new() - anim_player.name = "AnimationPlayer" - model.add_child(anim_player) + match character_class.main_stat: + CharacterClass.MainStat.STRENGTH: # Krieger + available_skills.append({ + "id": "heavy_strike", + "name": "Mächtiger Schlag", + "description": "Schlägt den Gegner mit voller Kraft (10-15 Schaden, 3s CD).", + "icon": "res://icons/heavy_strike_icon.svg", + "cooldown": HEAVY_STRIKE_COOLDOWN, + "cast_time": 0.0, + }) - # AnimationLibrary holen oder erstellen - var lib: AnimationLibrary - if anim_player.has_animation_library(""): - lib = anim_player.get_animation_library("") - else: - lib = AnimationLibrary.new() - anim_player.add_animation_library("", lib) + CharacterClass.MainStat.INTELLIGENCE: # Magier + available_skills.append({ + "id": "frostbolt", + "name": "Frostblitz", + "description": "Schießt einen Frostblitz auf den Feind (2s Channeling).", + "icon": "res://icons/heavy_strike_icon.svg", # Temp-Icon + "cooldown": 0.0, + "cast_time": 2.0, + }) - # Animationen aus separaten FBX-Dateien laden - for anim_id in ANIMATION_FILES: - var scene = load(ANIMATION_FILES[anim_id]) as PackedScene - if scene == null: - continue - var instance = scene.instantiate() - var source_ap = _find_node_by_class(instance, "AnimationPlayer") - if source_ap: - var names = source_ap.get_animation_list() - if names.size() > 0: - var anim = source_ap.get_animation(names[0]) - # Endlos-Animationen loopen - if anim_id in ["walk", "walk_back", "strafe_left", "strafe_right", "idle"]: - anim.loop_mode = Animation.LOOP_LINEAR - if lib.has_animation(anim_id): - lib.remove_animation(anim_id) - lib.add_animation(anim_id, anim) - instance.queue_free() + CharacterClass.MainStat.AGILITY: # Schurke + available_skills.append({ + "id": "quick_strike", + "name": "Schnellschlag", + "description": "Blitzschneller Angriff (1s CD).", + "icon": "res://icons/autoattack_icon.svg", # Temp-Icon + "cooldown": 1.0, + "cast_time": 0.0, + }) - # Signal für Walk-Kette: Start Walking → Walking - anim_player.animation_finished.connect(_on_animation_finished) + # Standard-Belegung: Slot 0 = Autoattack, Slot 1 = erster Skill + action_bar[0] = "autoattack" + if available_skills.size() > 1: + action_bar[1] = available_skills[1]["id"] -# Callback wenn eine Animation fertig ist -func _on_animation_finished(anim_name: StringName): - if anim_name == "start_walk" and walk_state == "start": - # Start Walking fertig → Walking (loop) starten - walk_state = "walking" - current_anim = "walk" - anim_player.play("walk") - elif anim_name == "stop_walk" and walk_state == "stop": - # Stop Walking fertig → Idle - walk_state = "" - current_anim = "idle" - anim_player.play("idle") +# ═══════════════════════════════════════════════════════════════ +# STAT-BERECHNUNG +# ═══════════════════════════════════════════════════════════════ -# Rekursiv nach einem Node einer bestimmten Klasse suchen -func _find_node_by_class(node: Node, class_name_str: String) -> Node: - for child in node.get_children(): - if child.get_class() == class_name_str: - return child - var result = _find_node_by_class(child, class_name_str) - if result: - return result - return null - -# Animation basierend auf Bewegungszustand abspielen -func _update_animation(input_dir: Vector2): - if anim_player == null: - return - - # Angriffs-/Death-Animation nicht unterbrechen - if anim_player.is_playing() and current_anim in ["autoattack", "heavy_strike", "die"]: - return - - var is_walking_forward = input_dir.y < -0.1 or (input_dir.length() > 0 and abs(input_dir.y) <= 0.1 and abs(input_dir.x) <= 0.1) - var is_moving = input_dir.length() > 0 - - # Vorwärts-Laufen: Start Walking → Walking → Stop Walking - if is_walking_forward: - if walk_state == "": - # Loslaufen: Start Walking abspielen - walk_state = "start" - current_anim = "start_walk" - anim_player.play("start_walk") - elif walk_state == "start" and not anim_player.is_playing(): - # Start Walking fertig → Walking (loop) - walk_state = "walking" - current_anim = "walk" - anim_player.play("walk") - elif walk_state == "stop": - # War gerade am Anhalten, wieder loslaufen - walk_state = "start" - current_anim = "start_walk" - anim_player.play("start_walk") - # walking state: walk loopt automatisch - return - - # Nicht mehr vorwärts → Stop Walking wenn nötig - if walk_state == "start" or walk_state == "walking": - if not is_moving: - walk_state = "stop" - current_anim = "stop_walk" - anim_player.play("stop_walk") - return - else: - # Wechsel zu anderer Bewegung (rückwärts, strafe) - walk_state = "" - - # Stop Walking läuft noch - if walk_state == "stop": - if anim_player.is_playing(): - return # Warten bis Stop Walking fertig - walk_state = "" - - # Andere Animationen - var new_anim = "" - if not is_on_floor(): - new_anim = "jump" - elif is_moving: - if input_dir.y > 0.1: - new_anim = "walk_back" - elif input_dir.x < -0.1: - new_anim = "strafe_left" - elif input_dir.x > 0.1: - new_anim = "strafe_right" - else: - new_anim = "walk" - else: - new_anim = "idle" - - # Wenn Animation ausgelaufen ist, zurücksetzen - if not anim_player.is_playing(): - current_anim = "" - - if new_anim != current_anim: - current_anim = new_anim - if anim_player.has_animation(new_anim): - anim_player.play(new_anim) - else: - anim_player.stop() - -# Einmalige Animation abspielen (Angriff, Tod, etc.) -func _play_attack_anim(anim_name: String): - if anim_player == null: - return - if anim_player.has_animation(anim_name): - current_anim = anim_name - walk_state = "" # Walk-Kette unterbrechen - anim_player.stop() - anim_player.play(anim_name) - -# Stats basierend auf Klasse und Level berechnen func _calculate_stats(): if character_class == null: - # Fallback ohne Klasse - strength = 10 - agility = 10 - intelligence = 10 - stamina = 10 - max_hp = 100 - max_resource = 0 return - # Stats = Basis + (Level-1) * Zuwachs pro Level - var levels_gained = level - 1 - strength = character_class.base_strength + int(levels_gained * character_class.strength_per_level) - agility = character_class.base_agility + int(levels_gained * character_class.agility_per_level) - intelligence = character_class.base_intelligence + int(levels_gained * character_class.intelligence_per_level) - stamina = character_class.base_stamina + int(levels_gained * character_class.stamina_per_level) + # Basis-Stats aus Klasse + Level + strength = character_class.base_strength + int(character_class.strength_per_level * (level - 1)) + agility = character_class.base_agility + int(character_class.agility_per_level * (level - 1)) + intelligence = character_class.base_intelligence + int(character_class.intelligence_per_level * (level - 1)) + stamina = character_class.base_stamina + int(character_class.stamina_per_level * (level - 1)) + + # Stats aus Equipment addieren + armor = 0 + for slot in equipment: + var item = equipment[slot] + if item == null: + continue + strength += item.strength + agility += item.agility + intelligence += item.intelligence + stamina += item.stamina + armor += item.armor # HP aus Stamina berechnen + var old_max = max_hp max_hp = stamina * CharacterClass.HP_PER_STAMINA - # Klassen-Ressource berechnen - _calculate_resource() + # HP proportional anpassen + if old_max > 0: + current_hp = int(current_hp * float(max_hp) / float(old_max)) + current_hp = clamp(current_hp, 0, max_hp) - # Equipment-Boni hinzufügen - _apply_equipment_stats() - - print("Stats berechnet - STR: ", strength, " AGI: ", agility, " INT: ", intelligence, " STA: ", stamina, " ARM: ", armor, " HP: ", max_hp, " RES: ", max_resource) - -# Skills klassenabhängig aufbauen -func _init_class_skills(): - available_skills = [AUTOATTACK_SKILL.duplicate()] - if character_class and character_class.resource_type == CharacterClass.ResourceType.MANA: - # Magier: Autoattack + Zauberstab + Frostblitz - available_skills.append_array(MAGE_SKILLS.duplicate(true)) - action_bar_items[0] = "wand" - action_bar_items[1] = "frostbolt" - action_bar_items[2] = "autoattack" - else: - # Krieger/Schurke: Autoattack + Heavy Strike - available_skills.append_array(MELEE_SKILLS.duplicate(true)) - action_bar_items[0] = "autoattack" - action_bar_items[1] = "heavy_strike" - -# Klassen-Ressource berechnen (Mana aus INT, Energie fix, Wut fix) -func _calculate_resource(): - if character_class == null or character_class.resource_type == CharacterClass.ResourceType.NONE: - max_resource = 0 - return + # Ressource berechnen match character_class.resource_type: CharacterClass.ResourceType.MANA: max_resource = character_class.base_resource + intelligence * CharacterClass.MANA_PER_INT - CharacterClass.ResourceType.ENERGY: - max_resource = character_class.base_resource # Fix, skaliert nicht - CharacterClass.ResourceType.RAGE: - max_resource = character_class.base_resource # Fix, skaliert nicht + CharacterClass.ResourceType.RAGE, CharacterClass.ResourceType.ENERGY: + max_resource = character_class.base_resource + _: + max_resource = 0 + + current_resource = clamp(current_resource, 0, max_resource) -# Name der Klassen-Ressource func get_resource_name() -> String: if character_class == null: return "" match character_class.resource_type: - CharacterClass.ResourceType.MANA: return "Mana" + CharacterClass.ResourceType.MANA: return "Mana" + CharacterClass.ResourceType.RAGE: return "Wut" CharacterClass.ResourceType.ENERGY: return "Energie" - CharacterClass.ResourceType.RAGE: return "Wut" return "" -# Equipment-Stats auf Charakter anwenden -func _apply_equipment_stats(): - armor = 0 - haste = 0.0 - var bonus_str = 0 - var bonus_agi = 0 - var bonus_int = 0 - var bonus_sta = 0 +# ═══════════════════════════════════════════════════════════════ +# EQUIPMENT +# ═══════════════════════════════════════════════════════════════ - for slot in equipment.keys(): - var item = equipment[slot] - if item != null: - armor += item.armor - haste += item.haste - bonus_str += item.strength - bonus_agi += item.agility - bonus_int += item.intelligence - bonus_sta += item.stamina - - strength += bonus_str - agility += bonus_agi - intelligence += bonus_int - stamina += bonus_sta - - # HP und Ressource neu berechnen mit Equipment-Boni - max_hp = stamina * CharacterClass.HP_PER_STAMINA - _calculate_resource() - -# Equipment anlegen +# Item anlegen — gibt das alte Item zurück (oder null) func equip_item(item: Equipment) -> Equipment: - var old_item = equipment[item.slot] - equipment[item.slot] = item - _calculate_stats() - # HP proportional anpassen - if max_hp > 0: - current_hp = mini(current_hp, max_hp) - hud.update_health(current_hp, max_hp) - character_panel.update_stats(self) - print("Ausgerüstet: ", item.item_name, " in Slot ", Equipment.get_slot_name(item.slot)) - return old_item - -# Equipment ablegen -func unequip_slot(slot: Equipment.Slot) -> Equipment: + var slot = item.slot var old_item = equipment[slot] - if old_item == null: - return null - equipment[slot] = null + equipment[slot] = item _calculate_stats() - current_hp = mini(current_hp, max_hp) hud.update_health(current_hp, max_hp) - character_panel.update_stats(self) - print("Abgelegt: ", old_item.item_name) + if max_resource > 0: + hud.update_resource(current_resource, max_resource, get_resource_name()) + if character_panel and character_panel.panel_visible: + character_panel.update_stats(self) return old_item -# Ausgerüstete Waffe holen func get_equipped_weapon() -> Equipment: return equipment[Equipment.Slot.WEAPON] -# Main-Stat für Schadensberechnung holen -func get_main_stat() -> int: - if character_class == null: - return 10 - match character_class.main_stat: - CharacterClass.MainStat.STRENGTH: - return strength - CharacterClass.MainStat.AGILITY: - return agility - CharacterClass.MainStat.INTELLIGENCE: - return intelligence - return 10 - -# XP erhalten und Level-Up prüfen -func gain_xp(amount: int): - current_xp += amount - print("+" , amount, " XP (", current_xp, "/", xp_to_next_level, ")") - - while current_xp >= xp_to_next_level: - _level_up() - - hud.update_level(level, current_xp, xp_to_next_level) - -# Level-Up durchführen -func _level_up(): - current_xp -= xp_to_next_level - level += 1 - xp_to_next_level = _calculate_xp_for_level(level + 1) - - # Stats neu berechnen - _calculate_stats() - - # HP und Ressource vollständig auffüllen bei Level-Up - current_hp = max_hp - current_resource = max_resource - - hud.update_health(current_hp, max_hp) - hud.update_resource(current_resource, max_resource, get_resource_name()) - # Character Panel aktualisieren falls offen - character_panel.update_stats(self) - print("LEVEL UP! Jetzt Level ", level, " - HP und Ressource voll aufgefüllt!") - -# XP-Kurve: Jedes Level braucht mehr XP -func _calculate_xp_for_level(target_level: int) -> int: - return 100 * target_level # Level 2: 100, Level 3: 200, etc. - -# Handler für HUD-Slot-Klicks -func _on_slot_clicked(slot_index: int): - _use_action_slot(slot_index) - -# Slot aus Aktionsleiste entfernen (rausgezogen) - Item/Skill bleibt verfügbar -func _on_slot_drag_removed(slot_index: int): - action_bar_items[slot_index] = null - hud.clear_slot_icon(slot_index) - hud.set_slot_stack_count(slot_index, 0) - print("Slot " + str(slot_index + 1) + " geleert") - -# Zwei Slots tauschen -func _on_slot_drag_swapped(from_slot: int, to_slot: int): - var temp = action_bar_items[from_slot] - action_bar_items[from_slot] = action_bar_items[to_slot] - action_bar_items[to_slot] = temp - _refresh_action_slot(from_slot) - _refresh_action_slot(to_slot) - -# Skill per ID auf Slot legen -func assign_skill_to_action_bar(slot_index: int, skill_id: String): - action_bar_items[slot_index] = skill_id - _refresh_action_slot(slot_index) - print(skill_id + " auf Slot " + str(slot_index + 1) + " gelegt") - -# Skill-Info anhand ID holen -func get_skill_info(skill_id: String) -> Dictionary: - for skill in available_skills: - if skill["id"] == skill_id: - return skill - return {} - -# Cooldown für einen Slot ermitteln -func _get_slot_cooldown(slot_index: int) -> float: - var entry = action_bar_items[slot_index] - if entry is String: - match entry: - "autoattack": - return global_cooldown - "wand": - return global_cooldown - "heavy_strike": - return heavy_strike_cooldown - "frostbolt": - return frostbolt_cooldown - elif entry is Consumable: - return potion_cooldown - return 0.0 - -func _refresh_action_slot(slot_index: int): - var entry = action_bar_items[slot_index] - if entry is String: - # Skill - var info = get_skill_info(entry) - if info.size() > 0: - hud.set_slot_icon(slot_index, info["icon"]) - else: - hud.clear_slot_icon(slot_index) - hud.set_slot_stack_count(slot_index, 0) - elif entry is Consumable and entry.icon: - hud.set_slot_icon_texture(slot_index, entry.icon) - hud.set_slot_stack_count(slot_index, entry.stack_size) - else: - hud.clear_slot_icon(slot_index) - hud.set_slot_stack_count(slot_index, 0) - -func _use_action_slot(slot_index: int): - var entry = action_bar_items[slot_index] - if entry is String: - # Skill ausführen - match entry: - "autoattack": - if target != null and global_cooldown <= 0: - wand_active = false # Zauberstab deaktivieren - start_autoattack() - perform_autoattack() - "wand": - if target != null and global_cooldown <= 0: - autoattack_active = false # Autoattack deaktivieren - start_wand() - perform_wand_attack() - "heavy_strike": - use_heavy_strike() - "frostbolt": - use_frostbolt() - elif entry is Consumable: - if use_consumable(entry): - if entry.stack_size <= 0: - inventory.remove_item(entry) - _update_action_bar_stacks() - -# Schaden am Spieler abziehen und HP-Leiste aktualisieren -func take_damage(amount): - current_hp = clamp(current_hp - amount, 0, max_hp) - hud.update_health(current_hp, max_hp) - if is_casting: - _cancel_cast() - if current_hp <= 0: - die() - -# Schaden mit Rüstung und Level-Differenz berechnen -func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int: - var damage = float(raw_damage) - - # Rüstung reduziert nur Nahkampfschaden - if is_melee and armor > 0: - var armor_reduction = float(armor) / (float(armor) + 50.0) - damage = damage * (1.0 - armor_reduction) - - # Level-Differenz Modifikator (Gegner höheres Level = mehr Schaden) - var level_diff = attacker_level - level - var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD) - damage = damage * (1.0 + level_mod) - - return maxi(1, int(damage)) - -# Schaden mit vollem Schadenssystem nehmen -func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true): - var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee) - print("Spieler nimmt Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)") - take_damage(final_damage) - -# HP heilen und HP-Leiste aktualisieren -func heal(amount): - current_hp = clamp(current_hp + amount, 0, max_hp) - hud.update_health(current_hp, max_hp) - -# Ressource wiederherstellen (Mana/Energie/Wut) -func restore_mana(amount): - current_resource = clamp(current_resource + amount, 0, max_resource) - hud.update_resource(current_resource, max_resource, get_resource_name()) - -# Ressource verbrauchen -func spend_resource(amount) -> bool: - if current_resource < amount: - print("Nicht genug " + get_resource_name() + "!") - return false - current_resource = clamp(current_resource - amount, 0, max_resource) - hud.update_resource(current_resource, max_resource, get_resource_name()) - return true - -# Consumable benutzen (Trank etc.) -func use_consumable(consumable: Consumable) -> bool: - if potion_cooldown > 0: - print("Trank noch im Cooldown!") - return false - if consumable.use(self): - potion_cooldown = consumable.cooldown - if consumable.stack_size <= 0: - return true # Verbraucht - return false - -# Consumable auf Aktionsleiste legen -func assign_to_action_bar(slot_index: int, consumable: Consumable): - if slot_index < 2 or slot_index > 8: - return # Slot 0+1 sind reserviert für Skills - action_bar_items[slot_index] = consumable - if consumable and consumable.icon: - hud.set_slot_icon_texture(slot_index, consumable.icon) - hud.set_slot_stack_count(slot_index, consumable.stack_size) - else: - hud.clear_slot_icon(slot_index) - hud.set_slot_stack_count(slot_index, 0) - -# Aktionsleiste Stack-Counts aktualisieren -func _update_action_bar_stacks(): - for i in range(2, 9): - var item = action_bar_items[i] - if item is Consumable: - if item.stack_size <= 0: - action_bar_items[i] = null - hud.clear_slot_icon(i) - hud.set_slot_stack_count(i, 0) - else: - hud.set_slot_stack_count(i, item.stack_size) - -# Loot empfangen und Fenster anzeigen -func receive_loot(loot: Dictionary, world_pos: Vector3): - loot_window.show_loot(loot, world_pos) - -func die(): - print("Spieler gestorben!") - _play_attack_anim("die") - -# Schaden basierend auf ausgerüsteter Waffe + Main-Stat Skalierung func get_attack_damage() -> int: var weapon = get_equipped_weapon() - var base_damage: int if weapon == null: - # Unbewaffneter Schaden klassenabhängig if character_class: - base_damage = randi_range(character_class.unarmed_min_damage, character_class.unarmed_max_damage) - else: - base_damage = 1 - else: - base_damage = randi_range(weapon.min_damage, weapon.max_damage) + return randi_range(character_class.unarmed_min_damage, character_class.unarmed_max_damage) + return 1 + # Main-Stat-Bonus + var bonus = 0 + if character_class: + match character_class.main_stat: + CharacterClass.MainStat.STRENGTH: bonus = int(strength * CharacterClass.DAMAGE_PER_MAIN_STAT) + CharacterClass.MainStat.AGILITY: bonus = int(agility * CharacterClass.DAMAGE_PER_MAIN_STAT) + CharacterClass.MainStat.INTELLIGENCE: bonus = int(intelligence * CharacterClass.DAMAGE_PER_MAIN_STAT) + return randi_range(weapon.min_damage, weapon.max_damage) + bonus - # Schaden skaliert mit Main-Stat - var stat_bonus = int(get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT) - return base_damage + stat_bonus - -# Aktuellen GCD berechnen (mit Haste-Modifikator) -func get_current_gcd() -> float: - var weapon = get_equipped_weapon() - var base_speed: float - if weapon == null: - # Unbewaffnete Angriffsgeschwindigkeit klassenabhängig - if character_class: - base_speed = character_class.unarmed_attack_speed - else: - base_speed = BASE_GCD - else: - base_speed = weapon.attack_speed - - # Haste reduziert den GCD: GCD = Basis / (1 + Haste) - # Bei 0.5 Haste (50%): 1.5s / 1.5 = 1.0s - return base_speed / (1.0 + haste) - -# DPS berechnen (für Anzeige) -func get_dps() -> float: - var weapon = get_equipped_weapon() - var avg_damage: float - if weapon == null: - # Unbewaffneter Durchschnittsschaden klassenabhängig - if character_class: - avg_damage = (character_class.unarmed_min_damage + character_class.unarmed_max_damage) / 2.0 - else: - avg_damage = 1.0 - else: - avg_damage = (weapon.min_damage + weapon.max_damage) / 2.0 - - var stat_bonus = get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT - var total_damage = avg_damage + stat_bonus - var gcd = get_current_gcd() - - # DPS = Schaden / GCD - return total_damage / gcd - -# Reichweite basierend auf ausgerüsteter Waffe (Nahkampf) func get_attack_range() -> float: var weapon = get_equipped_weapon() if weapon == null: - return 3.0 + return 1.5 return weapon.weapon_range -# Ziel markieren — start_attack=true startet sofort die Autoattack -func set_target(new_target, start_attack: bool = false): - if target != null and is_instance_valid(target): - target.hide_health() - target = new_target - target.show_health() - print("Ziel markiert: ", target.name) - if start_attack: - _start_default_attack() +func get_attack_cooldown() -> float: + var weapon = get_equipped_weapon() + if weapon == null: + if character_class: + return character_class.unarmed_attack_speed + return 1.5 + var speed = weapon.attack_speed + if character_class: + speed *= max(0.1, 1.0 - (agility * 0.002)) # Agilität reduziert Cooldown leicht + return speed -# Ziel komplett aufheben und Autoattack stoppen -func clear_target(): - if target != null and is_instance_valid(target): - target.hide_health() - target = null - autoattack_active = false - wand_active = false - print("Ziel aufgehoben, Angriff gestoppt") - -# Standard-Angriff starten (Rechtsklick): Magier=Zauberstab, Rest=Autoattack -func _start_default_attack(): - if character_class and character_class.resource_type == CharacterClass.ResourceType.MANA: - start_wand() - if global_cooldown <= 0: - perform_wand_attack() +func get_dps() -> float: + var weapon = get_equipped_weapon() + var avg_dmg: float + if weapon: + avg_dmg = float(weapon.min_damage + weapon.max_damage) / 2.0 + elif character_class: + avg_dmg = float(character_class.unarmed_min_damage + character_class.unarmed_max_damage) / 2.0 else: - start_autoattack() - if global_cooldown <= 0: - perform_autoattack() + avg_dmg = 1.5 + var cooldown = get_attack_cooldown() + if cooldown <= 0: + return 0.0 + return avg_dmg / cooldown -# Autoattack aktivieren -func start_autoattack(): - autoattack_active = true - print("Autoattack aktiviert") +# ═══════════════════════════════════════════════════════════════ +# HP / HEILUNG / TOD +# ═══════════════════════════════════════════════════════════════ -# Autoattack deaktivieren -func stop_autoattack(): - autoattack_active = false - print("Autoattack deaktiviert") +func take_damage(amount: int): + var effective = max(1, amount - int(armor * 0.1)) + current_hp = clamp(current_hp - effective, 0, max_hp) + hud.update_health(current_hp, max_hp) + if current_hp <= 0: + die() -# Zauberstab aktivieren (deaktiviert Autoattack) -func start_wand(): - wand_active = true - autoattack_active = false - print("Zauberstab aktiviert") +func heal(amount: int): + current_hp = clamp(current_hp + amount, 0, max_hp) + hud.update_health(current_hp, max_hp) -# Zauberstab deaktivieren -func stop_wand(): - wand_active = false - print("Zauberstab deaktiviert") +func restore_mana(amount: int): + current_resource = clamp(current_resource + amount, 0, max_resource) + hud.update_resource(current_resource, max_resource, get_resource_name()) -# Zauberstab-Angriff ausführen (Fernkampf, magisch) -func perform_wand_attack(): - if target == null or not is_instance_valid(target): - target = null - wand_active = false +func die(): + if is_dead: + return + is_dead = true + print("Spieler gestorben!") + _play_anim("death") + +# ═══════════════════════════════════════════════════════════════ +# XP / LEVELING +# ═══════════════════════════════════════════════════════════════ + +func gain_xp(amount: int): + current_xp += amount + print("+" + str(amount) + " XP") + while current_xp >= xp_to_next_level: + current_xp -= xp_to_next_level + _level_up() + hud.update_level(level, current_xp, xp_to_next_level) + +func _level_up(): + level += 1 + xp_to_next_level = int(xp_to_next_level * 1.5) + _calculate_stats() + current_hp = max_hp + current_resource = max_resource + hud.update_health(current_hp, max_hp) + hud.update_resource(current_resource, max_resource, get_resource_name()) + print("LEVEL UP! Jetzt Level " + str(level)) + +# ═══════════════════════════════════════════════════════════════ +# LOOT +# ═══════════════════════════════════════════════════════════════ + +func receive_loot(loot: Dictionary, world_pos: Vector3): + if loot_window: + loot_window.show_loot(loot, world_pos) + +# ═══════════════════════════════════════════════════════════════ +# AKTIONSLEISTE +# ═══════════════════════════════════════════════════════════════ + +# Skill auf Slot legen +func assign_skill_to_action_bar(slot_index: int, skill_id: String): + if slot_index < 0 or slot_index >= 9: + return + action_bar[slot_index] = skill_id + _refresh_action_slot(slot_index) + +# Consumable auf Slot legen +func assign_to_action_bar(slot_index: int, item: Consumable): + if slot_index < 0 or slot_index >= 9: + return + action_bar[slot_index] = item + _refresh_action_slot(slot_index) + +# Slot-Icon und Cooldown im HUD aktualisieren +func _refresh_action_slot(slot_index: int): + if slot_index < 0 or slot_index >= 9: + return + var slot_content = action_bar[slot_index] + + if slot_content == null: + hud.clear_slot_icon(slot_index) + hud.set_slot_stack_count(slot_index, 0) return - var distance = global_position.distance_to(target.global_position) - if distance <= WAND_RANGE: - var dmg = get_attack_damage() - if target.has_method("take_damage_from"): - target.take_damage_from(dmg, level, false) # Magisch, ignoriert Rüstung - else: - target.take_damage(dmg) - print("Zauberstab: ", dmg, " magischer Schaden") - _play_attack_anim("autoattack") - trigger_global_cooldown() + if slot_content is String: + # Skill + var skill = _find_skill(slot_content) + if skill: + if skill["icon"] != "": + hud.set_slot_icon(slot_index, skill["icon"]) + hud.set_slot_stack_count(slot_index, 0) + elif slot_content is Consumable: + # Consumable + if slot_content.icon: + hud.set_slot_icon_texture(slot_index, slot_content.icon) + hud.set_slot_stack_count(slot_index, slot_content.stack_size) + +# Stack-Counts aller Consumable-Slots aktualisieren +func _update_action_bar_stacks(): + for i in range(9): + if action_bar[i] is Consumable: + hud.set_slot_stack_count(i, action_bar[i].stack_size) + +# Slot geleert per Drag +func _on_slot_drag_removed(slot_index: int): + action_bar[slot_index] = null + hud.clear_slot_icon(slot_index) + hud.set_slot_stack_count(slot_index, 0) + +# Zwei Slots tauschen per Drag +func _on_slot_drag_swapped(from_slot: int, to_slot: int): + var temp = action_bar[from_slot] + action_bar[from_slot] = action_bar[to_slot] + action_bar[to_slot] = temp + _refresh_action_slot(from_slot) + _refresh_action_slot(to_slot) + +func _find_skill(skill_id: String) -> Dictionary: + for s in available_skills: + if s["id"] == skill_id: + return s + return {} + +# ═══════════════════════════════════════════════════════════════ +# SKILL-AUSFÜHRUNG +# ═══════════════════════════════════════════════════════════════ + +func _on_slot_clicked(slot_index: int): + var slot_content = action_bar[slot_index] + if slot_content == null: + return + if slot_content is String: + execute_skill(slot_content) + elif slot_content is Consumable: + _use_consumable_slot(slot_index, slot_content) + +func execute_skill(skill_id: String): + if is_dead or is_casting: + return + var skill = _find_skill(skill_id) + if skill.is_empty(): + return + + # GCD Check + if global_cooldown > 0: + print("GCD aktiv: %.1fs" % global_cooldown) + return + + # Skill-CD Check + if skill_cooldowns.get(skill_id, 0.0) > 0: + print(skill["name"] + " im Cooldown: %.1fs" % skill_cooldowns[skill_id]) + return + + # Ziel-Check für Angriffe + var needs_target = (skill_id != "") + if skill_id in ["autoattack", "heavy_strike", "quick_strike", "frostbolt"]: + if target == null or not is_instance_valid(target): + print("Kein Ziel!") + return + + # Cast-Zeit? + if skill["cast_time"] > 0: + _start_cast(skill_id, skill["cast_time"]) + return + + _apply_skill(skill_id) + +func _start_cast(skill_id: String, cast_time: float): + is_casting = true + cast_total = cast_time + cast_time_remaining = cast_time + pending_skill_id = skill_id + hud.show_castbar(skill_id, cast_time) + +func _cancel_cast(): + is_casting = false + cast_time_remaining = 0.0 + pending_skill_id = "" + hud.hide_castbar() + +func _apply_skill(skill_id: String): + match skill_id: + "autoattack": + start_autoattack() + perform_autoattack() + "heavy_strike": + _do_heavy_strike() + "quick_strike": + _do_quick_strike() + "frostbolt": + _do_frostbolt() + +# ─── Autoattack ─────────────────────────────────────────────── + +func start_autoattack(): + autoattack_active = true + +func stop_autoattack(): + autoattack_active = false -# Führt einen Autoattack aus (wird vom GCD-System aufgerufen) func perform_autoattack(): if target == null or not is_instance_valid(target): target = null autoattack_active = false return - var distance = global_position.distance_to(target.global_position) if distance <= get_attack_range(): var dmg = get_attack_damage() - if target.has_method("take_damage_from"): - target.take_damage_from(dmg, level, true) # Nahkampf - else: - target.take_damage(dmg) - print("Autoattack: ", dmg, " Schaden (GCD: %.2fs, DPS: %.1f)" % [get_current_gcd(), get_dps()]) - _play_attack_anim("autoattack") + target.take_damage(dmg) + print("Autoattack: " + str(dmg) + " Schaden") trigger_global_cooldown() + _play_anim_once("autoattack") + else: + print("Ziel zu weit entfernt") -# Global Cooldown auslösen (basierend auf Waffe + Haste) -func trigger_global_cooldown(): - global_cooldown = get_current_gcd() +# ─── Heavy Strike ───────────────────────────────────────────── -# Heavy Strike: Starker Angriff mit Cooldown -func use_heavy_strike(): +func _do_heavy_strike(): if target == null or not is_instance_valid(target): - print("Kein Ziel für Heavy Strike!") return - - # Nur Skill-eigener Cooldown Check (kein GCD-Check!) - if heavy_strike_cooldown > 0: - print("Heavy Strike noch im Cooldown: ", "%.1f" % heavy_strike_cooldown, "s") - return - var distance = global_position.distance_to(target.global_position) if distance > HEAVY_STRIKE_RANGE: - print("Ziel zu weit entfernt für Heavy Strike!") + print("Ziel zu weit für Heavy Strike!") return - - var base_damage = randi_range(HEAVY_STRIKE_DAMAGE_MIN, HEAVY_STRIKE_DAMAGE_MAX) - var stat_bonus = int(get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT) - var damage = base_damage + stat_bonus - # Neues Schadenssystem mit Rüstung und Level-Differenz - if target.has_method("take_damage_from"): - target.take_damage_from(damage, level, true) # true = Nahkampf - else: - target.take_damage(damage) - heavy_strike_cooldown = HEAVY_STRIKE_COOLDOWN - _play_attack_anim("heavy_strike") - trigger_global_cooldown() # GCD zurücksetzen damit Autoattack nicht sofort feuert - start_autoattack() # Autoattack nach Skill automatisch aktivieren - print("Heavy Strike! ", damage, " Rohschaden") - -# Frostblitz: Cast starten -func use_frostbolt(): - if is_casting: - return # Bereits am Casten - if target == null or not is_instance_valid(target): - print("Kein Ziel für Frostblitz!") - return - if frostbolt_cooldown > 0: - print("Frostblitz noch im Cooldown: ", "%.1f" % frostbolt_cooldown, "s") - return - if current_resource < FROSTBOLT_MANA_COST: - print("Nicht genug Mana für Frostblitz! (", current_resource, "/", FROSTBOLT_MANA_COST, ")") - return - var distance = global_position.distance_to(target.global_position) - if distance > FROSTBOLT_RANGE: - print("Ziel zu weit entfernt für Frostblitz!") - return - - # Cast starten - _start_cast("frostbolt", FROSTBOLT_CAST_TIME) - print("Frostblitz wird gewirkt... (", FROSTBOLT_CAST_TIME, "s)") - -# Frostblitz: Schaden anwenden nach erfolgreichem Cast -func _finish_frostbolt(): - if target == null or not is_instance_valid(target): - print("Ziel verloren!") - return - var distance = global_position.distance_to(target.global_position) - if distance > FROSTBOLT_RANGE: - print("Ziel zu weit entfernt!") - return - - # Mana abziehen - spend_resource(FROSTBOLT_MANA_COST) - - var base_damage = randi_range(FROSTBOLT_DAMAGE_MIN, FROSTBOLT_DAMAGE_MAX) - var stat_bonus = int(intelligence * CharacterClass.DAMAGE_PER_MAIN_STAT) - var damage = base_damage + stat_bonus - if target.has_method("take_damage_from"): - target.take_damage_from(damage, level, false) - else: - target.take_damage(damage) - frostbolt_cooldown = FROSTBOLT_COOLDOWN + var damage = randi_range(10, 15) + int(strength * 0.5) + target.take_damage(damage) + skill_cooldowns["heavy_strike"] = HEAVY_STRIKE_COOLDOWN trigger_global_cooldown() - start_wand() # Zauberstab nach Cast weiter aktiv - print("Frostblitz! ", damage, " magischer Schaden (", FROSTBOLT_MANA_COST, " Mana)") + start_autoattack() + print("Heavy Strike! " + str(damage) + " Schaden") + _play_anim_once("heavy_strike") -# Cast-System -func _start_cast(spell_id: String, cast_time: float): - is_casting = true - cast_spell_id = spell_id - cast_time_total = cast_time - cast_time_remaining = cast_time - autoattack_active = false # Autoattack pausieren während Cast - hud.show_castbar(spell_id, cast_time) +# ─── Quick Strike (Schurke) ─────────────────────────────────── -func _cancel_cast(): - if not is_casting: +func _do_quick_strike(): + if target == null or not is_instance_valid(target): return - is_casting = false - cast_spell_id = "" - cast_time_remaining = 0.0 - hud.hide_castbar() - print("Zauber unterbrochen!") + var distance = global_position.distance_to(target.global_position) + if distance > get_attack_range(): + print("Ziel zu weit für Quick Strike!") + return + var damage = get_attack_damage() + target.take_damage(damage) + skill_cooldowns["quick_strike"] = 1.0 + trigger_global_cooldown() + start_autoattack() + print("Quick Strike! " + str(damage) + " Schaden") + _play_anim_once("autoattack") -func _finish_cast(): - var spell = cast_spell_id - is_casting = false - cast_spell_id = "" - cast_time_remaining = 0.0 - hud.hide_castbar() - # Fertigen Zauber ausführen - match spell: - "frostbolt": - _finish_frostbolt() +# ─── Frostbolt (Magier) ─────────────────────────────────────── + +func _do_frostbolt(): + if target == null or not is_instance_valid(target): + return + var damage = randi_range(8, 14) + int(intelligence * 0.7) + target.take_damage(damage) + trigger_global_cooldown() + start_autoattack() + print("Frostblitz! " + str(damage) + " Schaden") + hud.hide_castbar() + +# ─── Consumable ─────────────────────────────────────────────── + +func use_consumable(item: Consumable) -> bool: + # Cooldown prüfen + if consumable_cooldowns.get(item.item_name, 0.0) > 0: + print(item.item_name + " im Cooldown!") + return false + var used = item.use(self) + if used: + consumable_cooldowns[item.item_name] = item.cooldown + return used + +func _use_consumable_slot(slot_index: int, item: Consumable): + if use_consumable(item): + if item.stack_size <= 0: + # Stack leer: Item aus Inventar und Slot entfernen + inventory.remove_item(item) + action_bar[slot_index] = null + hud.clear_slot_icon(slot_index) + else: + hud.set_slot_stack_count(slot_index, item.stack_size) + +# ─── GCD ────────────────────────────────────────────────────── + +func trigger_global_cooldown(): + global_cooldown = get_attack_cooldown() + +# ═══════════════════════════════════════════════════════════════ +# ZIELAUSWAHL +# ═══════════════════════════════════════════════════════════════ + +func set_target(new_target, start_attack: bool = false): + if target != null and is_instance_valid(target): + target.hide_health() + target = new_target + target.show_health() + print("Ziel: " + target.name) + if start_attack: + start_autoattack() -# Raycast von der Kamera auf Mausposition — trifft Gegner mit take_damage() func _try_select_target(start_attack: bool = false): var space_state = get_world_3d().direct_space_state var viewport = get_viewport() @@ -934,115 +703,298 @@ func _try_select_target(start_attack: bool = false): var result = space_state.intersect_ray(query) if result and result.collider.has_method("take_damage"): set_target(result.collider, start_attack) - elif not start_attack: - # Nur bei Linksklick ins Leere: Ziel deselektieren und Autoattack stoppen - # Rechtsklick wird für Kameradrehung verwendet - clear_target() + +# ═══════════════════════════════════════════════════════════════ +# ANIMATION SETUP +# ═══════════════════════════════════════════════════════════════ + +func _setup_animations(): + # AnimationPlayer aus importiertem Modell suchen + if model: + anim_player = model.find_child("AnimationPlayer", true, false) as AnimationPlayer + if anim_player == null: + push_warning("Player: Kein AnimationPlayer gefunden! Prüfe die Modell-Struktur.") + return + + _load_anim_from_fbx(ANIM_IDLE, "idle", true) + _load_anim_from_fbx(ANIM_WALK, "walk", true) + _load_anim_from_fbx(ANIM_RUN, "run", true) + _load_anim_from_fbx(ANIM_JUMP, "jump", false) + _load_anim_from_fbx(ANIM_AUTOATTACK, "autoattack", false) + _load_anim_from_fbx(ANIM_HEAVY_STRIKE, "heavy_strike", false) + _load_anim_from_fbx(ANIM_DEATH, "death", false) + _load_anim_from_fbx(ANIM_WALK_BACK, "walk_back", true) + _load_anim_from_fbx(ANIM_STRAFE_LEFT, "strafe_left", true) + _load_anim_from_fbx(ANIM_STRAFE_RIGHT, "strafe_right", true) + _load_anim_from_fbx(ANIM_RUN_STRAFE_LEFT, "run_strafe_left", true) + _load_anim_from_fbx(ANIM_RUN_STRAFE_RIGHT, "run_strafe_right", true) + _load_anim_from_fbx(ANIM_WALK_JUMP, "walk_jump", false) + _load_anim_from_fbx(ANIM_RUN_JUMP, "run_jump", false) + _load_anim_from_fbx(ANIM_ROLL, "roll", false) + _load_anim_from_fbx(ANIM_TURN_180, "turn_180", false) + + _play_anim("idle") + +func _load_anim_from_fbx(fbx_path: String, anim_name: String, loop: bool = false): + var scene = load(fbx_path) + if scene == null: + push_warning("Player: FBX nicht geladen: " + fbx_path) + return + var instance = scene.instantiate() + var ext_ap = instance.find_child("AnimationPlayer", true, false) as AnimationPlayer + if ext_ap == null: + instance.queue_free() + push_warning("Player: Kein AnimationPlayer in " + fbx_path) + return + var anim_list = ext_ap.get_animation_list() + if anim_list.is_empty(): + instance.queue_free() + return + var anim = ext_ap.get_animation(anim_list[0]) + # Root Motion entfernen: verhindert Snapping beim Loop + _strip_root_motion(anim) + # Loop-Modus setzen + anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE + if not anim_player.has_animation_library(""): + anim_player.add_animation_library("", AnimationLibrary.new()) + var lib = anim_player.get_animation_library("") + if not lib.has_animation(anim_name): + lib.add_animation(anim_name, anim) + instance.queue_free() + +func _strip_root_motion(anim: Animation): + # Mixamo speichert Root Motion entweder auf dem Armature-Node (kein Subname) + # ODER auf dem Hips-Knochen (hat Subname, aber bewegt sich in XZ vorwärts). + # Beide Fälle werden hier behandelt. + for i in range(anim.get_track_count() - 1, -1, -1): + if anim.track_get_type(i) != Animation.TYPE_POSITION_3D: + continue + var np: NodePath = anim.track_get_path(i) + if np.get_subname_count() == 0: + # Node-Position-Track (Armature-Root) → komplett entfernen + anim.remove_track(i) + else: + # Knochen-Position-Track — prüfen ob er sich in XZ bewegt + var key_count = anim.track_get_key_count(i) + if key_count < 2: + continue + var first: Vector3 = anim.track_get_key_value(i, 0) + var last: Vector3 = anim.track_get_key_value(i, key_count - 1) + var xz_delta = Vector2(last.x - first.x, last.z - first.z).length() + if xz_delta > 0.01: + # Hat XZ-Bewegung (z.B. Hips-Knochen bei Mixamo) → XZ nullen, Y behalten + for k in range(key_count): + var v: Vector3 = anim.track_get_key_value(i, k) + anim.track_set_key_value(i, k, Vector3(0.0, v.y, 0.0)) + +# ═══════════════════════════════════════════════════════════════ +# ANIMATION ABSPIELEN +# ═══════════════════════════════════════════════════════════════ + +func _play_anim(name: String, speed: float = 1.0): + if anim_player == null or not anim_player.has_animation(name): + return + if anim_player.current_animation == name and anim_player.speed_scale == speed: + return + anim_player.speed_scale = speed + anim_player.play(name, -1, 1.0, false) + +func _play_anim_once(name: String): + if anim_player == null or not anim_player.has_animation(name): + return + is_attacking = true + anim_player.speed_scale = 1.0 + anim_player.play(name, -1, 1.0, false) + await anim_player.animation_finished + is_attacking = false + + +func _do_roll(): + # Bewegungsrichtung beim Roll-Start erfassen + var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back") + var has_target = target != null and is_instance_valid(target) + if input_dir.length() > 0.1: + var ref_angle = rotation.y if (has_target or is_walking) else camera_pivot.world_yaw + roll_direction = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, ref_angle).normalized() + # Spieler in Roll-Richtung drehen, Kamera dabei kompensieren + var old_rot = rotation.y + rotation.y = atan2(-roll_direction.x, -roll_direction.z) + camera_pivot.rotation.y -= (rotation.y - old_rot) + else: + # Kein Input: nach vorne rollen + roll_direction = -global_transform.basis.z.normalized() + + is_rolling = true + roll_cooldown = ROLL_COOLDOWN + anim_player.speed_scale = 1.0 + anim_player.play("roll", -1, 1.0, false) + await anim_player.animation_finished + is_rolling = false + +func _update_movement_animation(is_moving: bool, input_dir: Vector2): + if is_attacking or is_dead or is_rolling: + return + var has_target = target != null and is_instance_valid(target) + if not is_on_floor(): + if not is_walking: + _play_anim("run_jump") + elif is_moving: + _play_anim("walk_jump") + else: + _play_anim("jump") + elif is_moving: + if not has_target and not is_walking: + var rmb = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) + if input_dir.y > 0.1: + _play_anim("walk_back") + elif rmb and input_dir.x < -0.1: + _play_anim("run_strafe_left") + elif rmb and input_dir.x > 0.1: + _play_anim("run_strafe_right") + else: + _play_anim("run") + elif input_dir.y > 0.1: + _play_anim("walk_back") + elif input_dir.x < -0.1: + _play_anim("strafe_left" if is_walking else "run_strafe_left") + elif input_dir.x > 0.1: + _play_anim("strafe_right" if is_walking else "run_strafe_right") + elif is_walking: + _play_anim("walk") + else: + _play_anim("run") + else: + _play_anim("idle") + +# ═══════════════════════════════════════════════════════════════ +# PHYSICS PROCESS (Hauptschleife) +# ═══════════════════════════════════════════════════════════════ func _physics_process(delta): - # Cast-System + if is_dead: + return + + # ── Cooldowns herunterzählen ────────────────────────────── + var gcd_was_active = global_cooldown > 0 + if global_cooldown > 0: + global_cooldown = max(0.0, global_cooldown - delta) + + for key in skill_cooldowns.keys(): + skill_cooldowns[key] = max(0.0, skill_cooldowns[key] - delta) + + for key in consumable_cooldowns.keys(): + consumable_cooldowns[key] = max(0.0, consumable_cooldowns[key] - delta) + + # ── Autoattack nach GCD ─────────────────────────────────── + if gcd_was_active and global_cooldown <= 0 and autoattack_active: + perform_autoattack() + + # ── Cast-System ─────────────────────────────────────────── if is_casting: cast_time_remaining -= delta - hud.update_castbar(cast_time_total - cast_time_remaining, cast_time_total) + hud.update_castbar(cast_total - cast_time_remaining, cast_total) if cast_time_remaining <= 0: - _finish_cast() + is_casting = false + _apply_skill(pending_skill_id) + pending_skill_id = "" - # Global Cooldown herunterzählen (gilt für alle Aktionen) - if global_cooldown > 0: - global_cooldown -= delta - - # Wenn GCD bereit und nicht am Casten - if global_cooldown <= 0 and not is_casting: - if wand_active: - perform_wand_attack() - elif autoattack_active: - perform_autoattack() - - # Skill-Cooldowns herunterzählen - if heavy_strike_cooldown > 0: - heavy_strike_cooldown -= delta - if frostbolt_cooldown > 0: - frostbolt_cooldown -= delta - if potion_cooldown > 0: - potion_cooldown -= delta - - # HUD Cooldowns aktualisieren - generisch pro Slot + # ── HUD Cooldowns aktualisieren ─────────────────────────── for i in range(9): - var cd = _get_slot_cooldown(i) - hud.set_slot_cooldown(i, cd) + var slot_content = action_bar[i] + if slot_content is String: + var cd = max(global_cooldown, skill_cooldowns.get(slot_content, 0.0)) + hud.set_slot_cooldown(i, cd) + elif slot_content is Consumable: + hud.set_slot_cooldown(i, consumable_cooldowns.get(slot_content.item_name, 0.0)) - # Schwerkraft + # ── Schwerkraft ─────────────────────────────────────────── if not is_on_floor(): velocity.y -= GRAVITY * delta - # Springen + # ── Springen ───────────────────────────────────────────── if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY if is_casting: _cancel_cast() - # Linksklick: nur markieren + # ── Zielauswahl ─────────────────────────────────────────── if Input.is_action_just_pressed("select_target"): _try_select_target(false) - - # Rechtsklick: markieren + angreifen if Input.is_action_just_pressed("ui_right_mouse"): _try_select_target(true) - # Aktionsleiste 1-9 — alle generisch über _use_action_slot + # ── Aktionsleiste (Tasten 1-9) ──────────────────────────── for i in range(9): - var action_name = "action_" + str(i + 1) - if Input.is_action_just_pressed(action_name): + if Input.is_action_just_pressed("action_" + str(i + 1)): hud.set_active_slot(i) - _use_action_slot(i) + _on_slot_clicked(i) - # TEST: T drücken = 10 Schaden + # ── Panel-Shortcuts ─────────────────────────────────────── + if Input.is_action_just_pressed("toggle_inventory") and inventory_panel: + inventory_panel.toggle() + if Input.is_action_just_pressed("toggle_character") and character_panel: + character_panel.toggle() + if character_panel.panel_visible: + character_panel.update_stats(self) + if Input.is_action_just_pressed("toggle_skills") and skill_panel: + skill_panel.toggle() + + # ── TEST ────────────────────────────────────────────────── if Input.is_action_just_pressed("test_damage"): take_damage(10) - # C drücken = Charakter-Panel öffnen/schließen - if Input.is_action_just_pressed("toggle_character"): - character_panel.update_stats(self) - character_panel.toggle() + # ── Roll Cooldown ───────────────────────────────────────── + if roll_cooldown > 0: + roll_cooldown = max(0.0, roll_cooldown - delta) - # I drücken = Inventar öffnen/schließen - if Input.is_action_just_pressed("toggle_inventory"): - inventory_panel.toggle() + # ── Walk Toggle ─────────────────────────────────────────── + if Input.is_action_just_pressed("walk_toggle"): + is_walking = !is_walking - # P drücken = Fähigkeiten-Panel öffnen/schließen - if Input.is_action_just_pressed("toggle_skills"): - skill_panel.toggle() + # ── Bewegung ───────────────────────────────────────────── + # get_vector: x = links(-)/rechts(+), y = vor(-)/zurück(+) + var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back") - # Eingabe - var input_dir = Vector2.ZERO - if Input.is_action_pressed("move_forward"): - input_dir.y -= 1 - if Input.is_action_pressed("move_back"): - input_dir.y += 1 - if Input.is_action_pressed("move_left"): - input_dir.x -= 1 - if Input.is_action_pressed("move_right"): - input_dir.x += 1 - - # Bewegung unterbricht Cast - if is_casting and input_dir.length() > 0: + # Cast unterbrechen bei Bewegung + if is_casting and input_dir.length() > 0.1: _cancel_cast() - # Bewegung relativ zur Kamera - var world_yaw = rotation.y + camera_pivot.rotation.y - var forward = Vector3(-sin(world_yaw), 0, -cos(world_yaw)).normalized() - var right = Vector3(cos(world_yaw), 0, -sin(world_yaw)).normalized() - var direction = (forward * -input_dir.y + right * input_dir.x) + var is_moving = input_dir.length() > 0.1 - velocity.x = direction.x * SPEED - velocity.z = direction.z * SPEED + var has_target = target != null and is_instance_valid(target) - # Animation aktualisieren - _update_animation(input_dir) + # ── Ausweichrolle ───────────────────────────────────────── + if Input.is_action_just_pressed("roll") and is_on_floor() and roll_cooldown <= 0 and not is_rolling: + _do_roll() - # RMB gehalten: Spieler schaut in Kamerarichtung - if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): - rotation.y = world_yaw - camera_pivot.rotation.y = 0 + # ── Bewegungsrichtung ──────────────────────────────────────────── + # Souls: world_yaw (stabil, dreht sich nicht mit Spieler mit) + # Lock-On/Walk: rotation.y (Strafe relativ zur Spielerausrichtung) + var dir3: Vector3 + if has_target or is_walking: + dir3 = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, rotation.y) + else: + dir3 = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, camera_pivot.world_yaw) + # ── Souls-Rotation: Zielwinkel aus Kamera-world_yaw berechnen ─── + # Nur vorwärts/diagonal (nicht S), nicht bei RMB, nicht Lock-On/Walk + if is_moving and not is_walking and not has_target and not is_rolling \ + and input_dir.y < 0.1 and not Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): + var desired_angle = atan2(-dir3.x, -dir3.z) + var diff = abs(angle_difference(rotation.y, desired_angle)) + if diff > deg_to_rad(150.0): + rotation.y = desired_angle + else: + rotation.y = lerp_angle(rotation.y, desired_angle, delta * 12.0) + + # ── Velocity ───────────────────────────────────────────── + var current_speed = SPEED if is_walking else SPRINT_SPEED + if is_rolling: + velocity.x = roll_direction.x * SPRINT_SPEED + velocity.z = roll_direction.z * SPRINT_SPEED + else: + velocity.x = dir3.x * current_speed + velocity.z = dir3.z * current_speed + + _update_movement_animation(is_moving, input_dir) move_and_slide() diff --git a/player.gd.uid b/player.gd.uid index 2b4ce41..5b05da9 100644 --- a/player.gd.uid +++ b/player.gd.uid @@ -1 +1 @@ -uid://b6n25e5fh82ra +uid://cwe8o6mk0hsi4 diff --git a/player.tscn b/player.tscn index 202851e..fd0a3d3 100644 --- a/player.tscn +++ b/player.tscn @@ -1,41 +1,31 @@ [gd_scene format=3 uid="uid://dniyuebl8yhtv"] -[ext_resource type="Script" uid="uid://b6n25e5fh82ra" path="res://player.gd" id="1_4flbx"] -[ext_resource type="PackedScene" uid="uid://da1w523lg7i2b" path="res://assets/models/warrior.fbx" id="2_hqtel"] -[ext_resource type="Script" uid="uid://bwtwon54po4w3" path="res://camera_pivot.gd" id="2_onrkg"] -[ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="4_hqtel"] -[ext_resource type="PackedScene" uid="uid://character_panel" path="res://character_panel.tscn" id="5_char_panel"] -[ext_resource type="PackedScene" uid="uid://inventory_panel" path="res://inventory_panel.tscn" id="6_inv_panel"] -[ext_resource type="PackedScene" uid="uid://loot_window" path="res://loot_window.tscn" id="7_loot_win"] -[ext_resource type="PackedScene" uid="uid://skill_panel" path="res://skill_panel.tscn" id="8_skill_panel"] +[ext_resource type="Script" uid="uid://cwe8o6mk0hsi4" path="res://player.gd" id="1_player"] +[ext_resource type="Script" uid="uid://bwtwon54po4w3" path="res://camera_pivot.gd" id="2_campivot"] +[ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="3_hud"] +[ext_resource type="PackedScene" uid="uid://daeym1tdcnhhd" path="res://assets/Warrior+Animation/castle_guard_01.fbx" id="4_model"] -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"] -radius = 0.6 -height = 3.0 +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] +radius = 0.4 +height = 1.8 -[node name="CharacterBody3D" type="CharacterBody3D" unique_id=937297102] -transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) -script = ExtResource("1_4flbx") +[node name="Player" type="CharacterBody3D" unique_id=1565111917] +script = ExtResource("1_player") -[node name="PlayerModel" parent="." unique_id=926968795 instance=ExtResource("2_hqtel")] -transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0) +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=481888033] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0) +shape = SubResource("CapsuleShape3D_1") -[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1359412306] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0) -shape = SubResource("CapsuleShape3D_4flbx") +[node name="Model" type="Node3D" parent="." unique_id=297754421] -[node name="CameraPivot" type="Node3D" parent="." unique_id=638440275] -script = ExtResource("2_onrkg") +[node name="castle_guard_01" parent="Model" unique_id=1352499997 instance=ExtResource("4_model")] +transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0) -[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1625345908] -transform = Transform3D(2, 0, 0, 0, 1.8126155, 0.84523654, 0, -0.84523654, 1.8126155, 0, 5, 5) +[node name="CameraPivot" type="Node3D" parent="." unique_id=2063743808] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0) +script = ExtResource("2_campivot") -[node name="HUD" parent="." unique_id=1901284390 instance=ExtResource("4_hqtel")] +[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1026091049] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7) -[node name="CharacterPanel" parent="." instance=ExtResource("5_char_panel")] - -[node name="InventoryPanel" parent="." instance=ExtResource("6_inv_panel")] - -[node name="LootWindow" parent="." instance=ExtResource("7_loot_win")] - -[node name="SkillPanel" parent="." instance=ExtResource("8_skill_panel")] +[node name="HUD" parent="." unique_id=904219504 instance=ExtResource("3_hud")] diff --git a/project.godot b/project.godot index 07b335d..3a0d0a5 100644 --- a/project.godot +++ b/project.godot @@ -112,6 +112,16 @@ toggle_skills={ "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":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null) ] } +roll={ +"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":4194325,"key_label":0,"unicode":0,"location":1,"echo":false,"script":null) +] +} +walk_toggle={ +"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":4194330,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [physics] diff --git a/world.gd b/world.gd index bd28344..aff6509 100644 --- a/world.gd +++ b/world.gd @@ -1,5 +1,14 @@ # World.gd -# Initialisiert die Spielwelt: weist dem Gegner den Spieler als Ziel zu +# ───────────────────────────────────────────────────────────────────────────── +# Spielwelt-Controller +# +# Verantwortlichkeiten: +# • Prozeduraler Boden-Shader (Schachbrettmuster, world-space stabil) +# • Prozeduraler Himmel (ProceduralSkyMaterial + WorldEnvironment) +# • Hauptmenü → Klassenauswahl → Spielinitialisierung +# • Startausrüstung je nach Klasse +# • Gegner-Setup, Loot-Weiterleitung, Respawn nach Tod +# ───────────────────────────────────────────────────────────────────────────── extends Node3D const ENEMY_SCENE = preload("res://enemy.tscn") @@ -16,13 +25,60 @@ const STARTER_CHEST = preload("res://equipment/leather_chest.tres") const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres") @onready var player = $Player +@onready var floor_mesh = $Boden/MeshInstance3D func _ready(): + _setup_floor_material() + _setup_sky() # Hauptmenü anzeigen var main_menu = MAIN_MENU.instantiate() add_child(main_menu) main_menu.start_game.connect(_on_start_game) +func _setup_sky(): + var sky_mat = ProceduralSkyMaterial.new() + sky_mat.sky_top_color = Color(0.15, 0.35, 0.75) + sky_mat.sky_horizon_color = Color(0.55, 0.75, 1.0) + sky_mat.ground_horizon_color = Color(0.35, 0.30, 0.25) + sky_mat.ground_bottom_color = Color(0.1, 0.1, 0.1) + sky_mat.sun_angle_max = 30.0 + sky_mat.sun_curve = 0.15 + + var sky = Sky.new() + sky.sky_material = sky_mat + + var env = Environment.new() + env.background_mode = Environment.BG_SKY + env.sky = sky + env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY + env.ambient_light_energy = 0.6 + env.tonemap_mode = Environment.TONE_MAPPER_FILMIC + + var world_env = WorldEnvironment.new() + world_env.environment = env + add_child(world_env) + +func _setup_floor_material(): + var shader = Shader.new() + shader.code = """ +shader_type spatial; +uniform float grid_size : hint_range(0.5, 10.0) = 2.0; +uniform vec4 color_a : source_color = vec4(0.22, 0.22, 0.22, 1.0); +uniform vec4 color_b : source_color = vec4(0.38, 0.38, 0.38, 1.0); + +void fragment() { + vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; + vec2 cell = floor(world_pos.xz / grid_size); + float checker = mod(cell.x + cell.y, 2.0); + ALBEDO = mix(color_a.rgb, color_b.rgb, checker); + ROUGHNESS = 0.85; + METALLIC = 0.0; +} +""" + var mat = ShaderMaterial.new() + mat.shader = shader + floor_mesh.material_override = mat + # Nach Hauptmenü: Klassenauswahl anzeigen func _on_start_game(): var menu = CLASS_SELECTION_MENU.instantiate() diff --git a/world.tscn b/world.tscn index 7b91309..e7023d9 100644 --- a/world.tscn +++ b/world.tscn @@ -2,7 +2,6 @@ [ext_resource type="PackedScene" uid="uid://dniyuebl8yhtv" path="res://player.tscn" id="1_f3sb7"] [ext_resource type="Script" uid="uid://cx56h588mfsk0" path="res://world.gd" id="1_tlwt5"] -[ext_resource type="PackedScene" uid="uid://cvojaeanxugfj" path="res://enemy.tscn" id="2_fj7yv"] [sub_resource type="BoxShape3D" id="BoxShape3D_fj7yv"] size = Vector3(200, 0.5, 200) @@ -21,16 +20,14 @@ script = ExtResource("1_tlwt5") shape = SubResource("BoxShape3D_fj7yv") [node name="MeshInstance3D" type="MeshInstance3D" parent="Boden" unique_id=1214783061] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2) mesh = SubResource("BoxMesh_tlwt5") [node name="Player" parent="." unique_id=937297102 instance=ExtResource("1_f3sb7")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0) [node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1394887598] transform = Transform3D(-45, 0, 0, 0, -45, 0, 0, 0, -45, 0, 0, 0) -[node name="Enemy" parent="." unique_id=332011146 instance=ExtResource("2_fj7yv")] -transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0.3, -7.038) - [node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=827244005] navigation_mesh = SubResource("NavigationMesh_fj7yv")