diff --git a/src/assets/audio/sfx/jsfxr/bird_sound.wav b/src/assets/audio/sfx/jsfxr/bird_sound.wav new file mode 100644 index 0000000..20621b2 Binary files /dev/null and b/src/assets/audio/sfx/jsfxr/bird_sound.wav differ diff --git a/src/assets/audio/sfx/jsfxr/bird_sound.wav.import b/src/assets/audio/sfx/jsfxr/bird_sound.wav.import new file mode 100644 index 0000000..8346155 --- /dev/null +++ b/src/assets/audio/sfx/jsfxr/bird_sound.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cjv4cf2kiomwo" +path="res://.godot/imported/bird_sound.wav-b46955955f9335504baeb1047dec1ef4.sample" + +[deps] + +source_file="res://assets/audio/sfx/jsfxr/bird_sound.wav" +dest_files=["res://.godot/imported/bird_sound.wav-b46955955f9335504baeb1047dec1ef4.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/jsfxr/explosion.wav b/src/assets/audio/sfx/jsfxr/explosion.wav new file mode 100644 index 0000000..2b675d7 Binary files /dev/null and b/src/assets/audio/sfx/jsfxr/explosion.wav differ diff --git a/src/assets/audio/sfx/jsfxr/explosion.wav.import b/src/assets/audio/sfx/jsfxr/explosion.wav.import new file mode 100644 index 0000000..880accc --- /dev/null +++ b/src/assets/audio/sfx/jsfxr/explosion.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://di766omly0jae" +path="res://.godot/imported/explosion.wav-2ed54abe1b2d0516e2a9c43072521c6e.sample" + +[deps] + +source_file="res://assets/audio/sfx/jsfxr/explosion.wav" +dest_files=["res://.godot/imported/explosion.wav-2ed54abe1b2d0516e2a9c43072521c6e.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/jsfxr/hitHurt (1).wav b/src/assets/audio/sfx/jsfxr/hitHurt (1).wav new file mode 100644 index 0000000..bfc1995 Binary files /dev/null and b/src/assets/audio/sfx/jsfxr/hitHurt (1).wav differ diff --git a/src/assets/audio/sfx/jsfxr/hitHurt (1).wav.import b/src/assets/audio/sfx/jsfxr/hitHurt (1).wav.import new file mode 100644 index 0000000..65098dc --- /dev/null +++ b/src/assets/audio/sfx/jsfxr/hitHurt (1).wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://8l0hx3sjh4ci" +path="res://.godot/imported/hitHurt (1).wav-6f28383ced03a2447f0c22e04c882b41.sample" + +[deps] + +source_file="res://assets/audio/sfx/jsfxr/hitHurt (1).wav" +dest_files=["res://.godot/imported/hitHurt (1).wav-6f28383ced03a2447f0c22e04c882b41.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/jsfxr/hitHurt.wav b/src/assets/audio/sfx/jsfxr/hitHurt.wav new file mode 100644 index 0000000..2ecef85 Binary files /dev/null and b/src/assets/audio/sfx/jsfxr/hitHurt.wav differ diff --git a/src/assets/audio/sfx/jsfxr/hitHurt.wav.import b/src/assets/audio/sfx/jsfxr/hitHurt.wav.import new file mode 100644 index 0000000..66057bf --- /dev/null +++ b/src/assets/audio/sfx/jsfxr/hitHurt.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://d0v3yf1mj4mxd" +path="res://.godot/imported/hitHurt.wav-fff3b933896d95faf3cb3183d82f9a2d.sample" + +[deps] + +source_file="res://assets/audio/sfx/jsfxr/hitHurt.wav" +dest_files=["res://.godot/imported/hitHurt.wav-fff3b933896d95faf3cb3183d82f9a2d.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/jsfxr/pickupCoin.wav b/src/assets/audio/sfx/jsfxr/pickupCoin.wav new file mode 100644 index 0000000..bb70ffe Binary files /dev/null and b/src/assets/audio/sfx/jsfxr/pickupCoin.wav differ diff --git a/src/assets/audio/sfx/jsfxr/pickupCoin.wav.import b/src/assets/audio/sfx/jsfxr/pickupCoin.wav.import new file mode 100644 index 0000000..fa92032 --- /dev/null +++ b/src/assets/audio/sfx/jsfxr/pickupCoin.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://bho5u81vx5gmg" +path="res://.godot/imported/pickupCoin.wav-b11b34a264bd8fc8a2381f3c47f6a13b.sample" + +[deps] + +source_file="res://assets/audio/sfx/jsfxr/pickupCoin.wav" +dest_files=["res://.godot/imported/pickupCoin.wav-b11b34a264bd8fc8a2381f3c47f6a13b.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364535327.wav b/src/assets/audio/sfx/wizard/animevox/boing_1769364535327.wav new file mode 100644 index 0000000..451acda Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/boing_1769364535327.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364535327.wav.import b/src/assets/audio/sfx/wizard/animevox/boing_1769364535327.wav.import new file mode 100644 index 0000000..88887c7 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/boing_1769364535327.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cmmjgvktb5j8n" +path="res://.godot/imported/boing_1769364535327.wav-acb4f31a69bec50ac3aa9aa01f6bdfea.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/boing_1769364535327.wav" +dest_files=["res://.godot/imported/boing_1769364535327.wav-acb4f31a69bec50ac3aa9aa01f6bdfea.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364542682.wav b/src/assets/audio/sfx/wizard/animevox/boing_1769364542682.wav new file mode 100644 index 0000000..936eee6 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/boing_1769364542682.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364542682.wav.import b/src/assets/audio/sfx/wizard/animevox/boing_1769364542682.wav.import new file mode 100644 index 0000000..1537c31 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/boing_1769364542682.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://b5o2pj5tykc88" +path="res://.godot/imported/boing_1769364542682.wav-bffa589516e2aa97aa405dd0c55f9efd.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/boing_1769364542682.wav" +dest_files=["res://.godot/imported/boing_1769364542682.wav-bffa589516e2aa97aa405dd0c55f9efd.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364598383.wav b/src/assets/audio/sfx/wizard/animevox/boing_1769364598383.wav new file mode 100644 index 0000000..14b88c6 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/boing_1769364598383.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364598383.wav.import b/src/assets/audio/sfx/wizard/animevox/boing_1769364598383.wav.import new file mode 100644 index 0000000..5698334 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/boing_1769364598383.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://bglxp21vu17ve" +path="res://.godot/imported/boing_1769364598383.wav-d03eba7d368599ddcbde2a695474497f.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/boing_1769364598383.wav" +dest_files=["res://.godot/imported/boing_1769364598383.wav-d03eba7d368599ddcbde2a695474497f.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364606981.wav b/src/assets/audio/sfx/wizard/animevox/boing_1769364606981.wav new file mode 100644 index 0000000..a97be64 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/boing_1769364606981.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364606981.wav.import b/src/assets/audio/sfx/wizard/animevox/boing_1769364606981.wav.import new file mode 100644 index 0000000..7bf9b55 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/boing_1769364606981.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://bpxdu1j6xlbw" +path="res://.godot/imported/boing_1769364606981.wav-ade7a7cf5e600ba22d23fb18eb5b5015.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/boing_1769364606981.wav" +dest_files=["res://.godot/imported/boing_1769364606981.wav-ade7a7cf5e600ba22d23fb18eb5b5015.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364651821.wav b/src/assets/audio/sfx/wizard/animevox/boing_1769364651821.wav new file mode 100644 index 0000000..cd62cfe Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/boing_1769364651821.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364651821.wav.import b/src/assets/audio/sfx/wizard/animevox/boing_1769364651821.wav.import new file mode 100644 index 0000000..c93dfe2 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/boing_1769364651821.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cm1d4xo5s76u5" +path="res://.godot/imported/boing_1769364651821.wav-502e03ca23e2fbeb991f072fca1dacbe.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/boing_1769364651821.wav" +dest_files=["res://.godot/imported/boing_1769364651821.wav-502e03ca23e2fbeb991f072fca1dacbe.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364702890.wav b/src/assets/audio/sfx/wizard/animevox/boing_1769364702890.wav new file mode 100644 index 0000000..48ab175 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/boing_1769364702890.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/boing_1769364702890.wav.import b/src/assets/audio/sfx/wizard/animevox/boing_1769364702890.wav.import new file mode 100644 index 0000000..9040028 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/boing_1769364702890.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://ccajvghy4x8wv" +path="res://.godot/imported/boing_1769364702890.wav-04301d3cc6eb2efe6c63ce7510d613c6.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/boing_1769364702890.wav" +dest_files=["res://.godot/imported/boing_1769364702890.wav-04301d3cc6eb2efe6c63ce7510d613c6.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/impact_1769286037120.wav b/src/assets/audio/sfx/wizard/animevox/impact_1769286037120.wav new file mode 100644 index 0000000..447f8e8 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/impact_1769286037120.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/impact_1769286037120.wav.import b/src/assets/audio/sfx/wizard/animevox/impact_1769286037120.wav.import new file mode 100644 index 0000000..8cfc9a4 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/impact_1769286037120.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://crfns2hgo7lxt" +path="res://.godot/imported/impact_1769286037120.wav-9efc780a91cd15d59f549ad1b1418ebb.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/impact_1769286037120.wav" +dest_files=["res://.godot/imported/impact_1769286037120.wav-9efc780a91cd15d59f549ad1b1418ebb.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/impact_1769364721189.wav b/src/assets/audio/sfx/wizard/animevox/impact_1769364721189.wav new file mode 100644 index 0000000..94210d9 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/impact_1769364721189.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/impact_1769364721189.wav.import b/src/assets/audio/sfx/wizard/animevox/impact_1769364721189.wav.import new file mode 100644 index 0000000..9af72d7 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/impact_1769364721189.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cij3m0pt7r5ga" +path="res://.godot/imported/impact_1769364721189.wav-1a31c60693df98332e9227a98533524a.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/impact_1769364721189.wav" +dest_files=["res://.godot/imported/impact_1769364721189.wav-1a31c60693df98332e9227a98533524a.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/slash_1769364693031.wav b/src/assets/audio/sfx/wizard/animevox/slash_1769364693031.wav new file mode 100644 index 0000000..6bd7a50 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/slash_1769364693031.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/slash_1769364693031.wav.import b/src/assets/audio/sfx/wizard/animevox/slash_1769364693031.wav.import new file mode 100644 index 0000000..02ce017 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/slash_1769364693031.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://beeix80itbwb5" +path="res://.godot/imported/slash_1769364693031.wav-2ad2e9bd0168a91968bf485e2a5cc1cb.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/slash_1769364693031.wav" +dest_files=["res://.godot/imported/slash_1769364693031.wav-2ad2e9bd0168a91968bf485e2a5cc1cb.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav new file mode 100644 index 0000000..d73bc29 Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav.import b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav.import new file mode 100644 index 0000000..a329749 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cag7alnbxymki" +path="res://.godot/imported/sparkle_1769364495063.wav-b61c6c787bf6a4f817a470f87062f181.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav" +dest_files=["res://.godot/imported/sparkle_1769364495063.wav-b61c6c787bf6a4f817a470f87062f181.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav new file mode 100644 index 0000000..d79e1ca Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav.import b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav.import new file mode 100644 index 0000000..20356a3 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://5rlt4sjeinxq" +path="res://.godot/imported/sparkle_1769364641348.wav-74b365bbefc51d0c6d49332149fc1d42.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/sparkle_1769364641348.wav" +dest_files=["res://.godot/imported/sparkle_1769364641348.wav-74b365bbefc51d0c6d49332149fc1d42.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav b/src/assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav new file mode 100644 index 0000000..8d9dd6e Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav.import b/src/assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav.import new file mode 100644 index 0000000..de83684 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cgrpvbrntl4lb" +path="res://.godot/imported/whoosh_1769286051051.wav-e59e3dabebb5cba9d2a412aeff0b4b12.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/whoosh_1769286051051.wav" +dest_files=["res://.godot/imported/whoosh_1769286051051.wav-e59e3dabebb5cba9d2a412aeff0b4b12.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav new file mode 100644 index 0000000..413ea8f Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav.import b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav.import new file mode 100644 index 0000000..d3fa793 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://ghi1yh80t2ae" +path="res://.godot/imported/whoosh_1769364574009.wav-6576e552fe9371f5faa6f2aba79af16f.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/whoosh_1769364574009.wav" +dest_files=["res://.godot/imported/whoosh_1769364574009.wav-6576e552fe9371f5faa6f2aba79af16f.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav new file mode 100644 index 0000000..2f7dbcf Binary files /dev/null and b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav differ diff --git a/src/assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav.import b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav.import new file mode 100644 index 0000000..8f80133 --- /dev/null +++ b/src/assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cuyvxnkjx6usa" +path="res://.godot/imported/whoosh_1769364646131.wav-280c0e6a35d590f20e89e29b6655ad2d.sample" + +[deps] + +source_file="res://assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav" +dest_files=["res://.godot/imported/whoosh_1769364646131.wav-280c0e6a35d590f20e89e29b6655ad2d.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/src/assets/gfx/RPG DUNGEON VOL 3.png b/src/assets/gfx/RPG DUNGEON VOL 3.png index 301b011..c4130b9 100644 Binary files a/src/assets/gfx/RPG DUNGEON VOL 3.png and b/src/assets/gfx/RPG DUNGEON VOL 3.png differ diff --git a/src/assets/gfx/RPG DUNGEON VOL 3.tres b/src/assets/gfx/RPG DUNGEON VOL 3.tres index d0c914e..bd7c918 100644 --- a/src/assets/gfx/RPG DUNGEON VOL 3.tres +++ b/src/assets/gfx/RPG DUNGEON VOL 3.tres @@ -35,9 +35,6 @@ polygon = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) [sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_bqa6v"] polygon = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) -[sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_c2t7l"] -polygon = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) - [sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_br5gx"] polygon = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) @@ -167,7 +164,6 @@ separation = Vector2i(1, 1) 1:2/0 = 0 1:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:2/0 = 0 -2:2/0/occlusion_layer_0/polygon_0/polygon = SubResource("OccluderPolygon2D_c2t7l") 3:2/0 = 0 3:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 4:2/0 = 0 diff --git a/src/assets/gfx/enemies/hand_monster.png b/src/assets/gfx/enemies/hand_monster.png index e5f3d73..e3c117e 100644 Binary files a/src/assets/gfx/enemies/hand_monster.png and b/src/assets/gfx/enemies/hand_monster.png differ diff --git a/src/assets/gfx/enemies/spider.png b/src/assets/gfx/enemies/spider.png new file mode 100644 index 0000000..69aabaf Binary files /dev/null and b/src/assets/gfx/enemies/spider.png differ diff --git a/src/assets/gfx/enemies/spider.png.import b/src/assets/gfx/enemies/spider.png.import new file mode 100644 index 0000000..e74f68d --- /dev/null +++ b/src/assets/gfx/enemies/spider.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmogyono02pl3" +path="res://.godot/imported/spider.png-80ad78c1ab808d8ac5dd1c6fa58f6c10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/gfx/enemies/spider.png" +dest_files=["res://.godot/imported/spider.png-80ad78c1ab808d8ac5dd1c6fa58f6c10.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/src/scenes/attack_spell_frostspike.tscn b/src/scenes/attack_spell_frostspike.tscn index 77158cf..0b2182d 100644 --- a/src/scenes/attack_spell_frostspike.tscn +++ b/src/scenes/attack_spell_frostspike.tscn @@ -1,30 +1,47 @@ [gd_scene format=3 uid="uid://d1u8p5rop4vye"] -[ext_resource type="Script" path="res://scripts/attack_spell_frostspike.gd" id="1_script"] +[ext_resource type="Script" uid="uid://cy3rt80axidqg" path="res://scripts/attack_spell_frostspike.gd" id="1_script"] [ext_resource type="Texture2D" uid="uid://bf158atxi7ucy" path="res://assets/gfx/fx/shade_spell_effects.png" id="2_tex"] +[ext_resource type="AudioStream" uid="uid://cij3m0pt7r5ga" path="res://assets/audio/sfx/wizard/animevox/impact_1769364721189.wav" id="3_y7fsv"] + +[sub_resource type="Gradient" id="Gradient_3jvtj"] +offsets = PackedFloat32Array(0.5839416, 0.7177616) +colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_y7fsv"] +gradient = SubResource("Gradient_3jvtj") +fill = 1 +fill_from = Vector2(0.517094, 0.50427353) +fill_to = Vector2(0.94871795, 0.102564104) [sub_resource type="RectangleShape2D" id="RectangleShape2D_frost"] size = Vector2(16, 16) -[node name="FrostspikeSpell" type="Node2D"] +[node name="FrostspikeSpell" type="Node2D" unique_id=2076098743] z_index = 4 script = ExtResource("1_script") -[node name="Sprite2D" type="Sprite2D" parent="."] +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=804594305] texture = ExtResource("2_tex") hframes = 105 vframes = 79 frame = 4413 -[node name="SpikeLight" type="PointLight2D" parent="."] +[node name="SpikeLight" type="PointLight2D" parent="." unique_id=1041040958] color = Color(0.35, 0.6, 1, 1) -energy = 1.0 +texture = SubResource("GradientTexture2D_y7fsv") texture_scale = 0.5 -enabled = true -[node name="Area2D" type="Area2D" parent="."] +[node name="Area2D" type="Area2D" parent="." unique_id=1349643659] collision_layer = 4 collision_mask = 3 -[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D" unique_id=358504371] shape = SubResource("RectangleShape2D_frost") + +[node name="SfxSpike" type="AudioStreamPlayer2D" parent="." unique_id=399258294] +stream = ExtResource("3_y7fsv") +max_distance = 1456.0 +attenuation = 5.4641595 +panning_strength = 1.06 +bus = &"Sfx" diff --git a/src/scenes/enemy_hand.tscn b/src/scenes/enemy_hand.tscn new file mode 100644 index 0000000..25fe888 --- /dev/null +++ b/src/scenes/enemy_hand.tscn @@ -0,0 +1,128 @@ +[gd_scene format=3 uid="uid://cx2jm5ua6evmb"] + +[ext_resource type="Script" uid="uid://bkeyokaahe2fd" path="res://scripts/enemy_hand.gd" id="1_hqcsv"] +[ext_resource type="Texture2D" uid="uid://dyid2xlxo1gnn" path="res://assets/gfx/enemies/hand_monster.png" id="1_lpach"] + +[sub_resource type="Animation" id="Animation_hqcsv"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [0] +} + +[sub_resource type="Animation" id="Animation_go7b8"] +resource_name = "emerge" +length = 0.4 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.099999994, 0.2, 0.3), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 1, +"values": [0, 1, 2, 3] +} + +[sub_resource type="Animation" id="Animation_ptw5w"] +resource_name = "idle" +length = 0.8 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.2, 0.4, 0.6), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 1, +"values": [4, 5, 6, 5] +} + +[sub_resource type="Animation" id="Animation_0vh4e"] +resource_name = "snatch" +length = 0.4 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.033333335, 0.06666667, 0.10000001, 0.13333334, 0.16666667, 0.20000002, 0.23333335, 0.26666668, 0.3, 0.33333334), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_ptw5w"] +_data = { +&"RESET": SubResource("Animation_hqcsv"), +&"emerge": SubResource("Animation_go7b8"), +&"idle": SubResource("Animation_ptw5w"), +&"snatch": SubResource("Animation_0vh4e") +} + +[sub_resource type="CircleShape2D" id="CircleShape2D_lpach"] +radius = 3.1622777 + +[sub_resource type="CircleShape2D" id="CircleShape2D_hqcsv"] +radius = 38.0 + +[sub_resource type="CircleShape2D" id="CircleShape2D_go7b8"] +radius = 5.0 + +[sub_resource type="CircleShape2D" id="CircleShape2D_ptw5w"] +radius = 58.30952 + +[node name="EnemyHand" type="CharacterBody2D" unique_id=512887809] +collision_mask = 64 +script = ExtResource("1_hqcsv") + +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=63819619] +position = Vector2(0, -6) +texture = ExtResource("1_lpach") +hframes = 4 +vframes = 4 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=939531210] +libraries/ = SubResource("AnimationLibrary_ptw5w") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=448410993] +shape = SubResource("CircleShape2D_lpach") + +[node name="EmergeArea" type="Area2D" parent="." unique_id=1688073969] +collision_layer = 0 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="EmergeArea" unique_id=1403525746] +shape = SubResource("CircleShape2D_hqcsv") + +[node name="GrabPlayerArea" type="Area2D" parent="." unique_id=812102876] + +[node name="CollisionShape2D" type="CollisionShape2D" parent="GrabPlayerArea" unique_id=1531888936] +shape = SubResource("CircleShape2D_go7b8") +debug_color = Color(0.70196074, 0, 0.05113532, 0.41960785) + +[node name="PlayerInterestArea" type="Area2D" parent="." unique_id=1613094277] +collision_layer = 0 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="PlayerInterestArea" unique_id=1847542345] +shape = SubResource("CircleShape2D_ptw5w") +debug_color = Color(0.70196074, 0.6745404, 0.69139016, 0.41960785) + +[connection signal="body_entered" from="EmergeArea" to="." method="_on_emerge_area_body_entered"] +[connection signal="body_entered" from="GrabPlayerArea" to="." method="_on_grab_player_area_body_entered"] +[connection signal="body_entered" from="PlayerInterestArea" to="." method="_on_player_interest_area_body_entered"] +[connection signal="body_exited" from="PlayerInterestArea" to="." method="_on_player_interest_area_body_exited"] diff --git a/src/scenes/floating_text.tscn b/src/scenes/floating_text.tscn index fa78935..8b99fd5 100644 --- a/src/scenes/floating_text.tscn +++ b/src/scenes/floating_text.tscn @@ -2,6 +2,25 @@ [ext_resource type="Script" uid="uid://dx5oym20rr2ei" path="res://scripts/floating_text.gd" id="1"] [ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="2_dmg_font"] +[ext_resource type="Shader" uid="uid://ldl7vaq5n13f" path="res://shaders/cloth.gdshader" id="2_ki2nc"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_27d3m"] +shader = ExtResource("2_ki2nc") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) [sub_resource type="Theme" id="Theme_floating_text"] default_font = ExtResource("2_dmg_font") @@ -13,6 +32,7 @@ script = ExtResource("1") [node name="ItemSprite" type="Sprite2D" parent="." unique_id=1657362510] visible = false +material = SubResource("ShaderMaterial_27d3m") offset = Vector2(0, -8) [node name="Label" type="Label" parent="." unique_id=1387220833] diff --git a/src/scenes/game_world.tscn b/src/scenes/game_world.tscn index f5d7047..3d37b10 100644 --- a/src/scenes/game_world.tscn +++ b/src/scenes/game_world.tscn @@ -6,6 +6,7 @@ [ext_resource type="Shader" uid="uid://dob36l1rwi2en" path="res://shaders/game_world.gdshader" id="4_bhwwd"] [ext_resource type="Script" uid="uid://wff5063ctp7g" path="res://scripts/debug_overlay.gd" id="5"] [ext_resource type="AudioStream" uid="uid://dthr2w8x0cj6v" path="res://assets/audio/sfx/ambience/wind-castle-loop.wav.mp3" id="6_6c6v5"] +[ext_resource type="AudioStream" uid="uid://ba6csajuxujrg" path="res://assets/audio/music/Gelhein - Evil.mp3" id="8_pdbwf"] [ext_resource type="TileSet" uid="uid://dqem5tbvooxrg" path="res://assets/gfx/RPG DUNGEON VOL 3.tres" id="9"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_pdbwf"] @@ -108,5 +109,11 @@ light_mask = 1048575 visibility_layer = 1048575 color = Color(0.69140625, 0.69140625, 0.69140625, 1) -[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="." unique_id=1141138343] +[node name="SfxWinds" type="AudioStreamPlayer2D" parent="." unique_id=1141138343] stream = ExtResource("6_6c6v5") +volume_db = -3.085 +autoplay = true + +[node name="BgMusic" type="AudioStreamPlayer2D" parent="." unique_id=628820950] +stream = ExtResource("8_pdbwf") +autoplay = true diff --git a/src/scenes/healing_effect.tscn b/src/scenes/healing_effect.tscn index ad83e97..5dd37c9 100644 --- a/src/scenes/healing_effect.tscn +++ b/src/scenes/healing_effect.tscn @@ -1,21 +1,38 @@ [gd_scene format=3 uid="uid://d3heal8fx2kqm"] -[ext_resource type="Script" path="res://scripts/healing_effect.gd" id="1_script"] +[ext_resource type="Script" uid="uid://27wuloudfkme" path="res://scripts/healing_effect.gd" id="1_script"] [ext_resource type="Texture2D" uid="uid://bf158atxi7ucy" path="res://assets/gfx/fx/shade_spell_effects.png" id="2_tex"] +[ext_resource type="AudioStream" uid="uid://cag7alnbxymki" path="res://assets/audio/sfx/wizard/animevox/sparkle_1769364495063.wav" id="3_5s4aw"] -[node name="HealingEffect" type="Node2D"] +[sub_resource type="Gradient" id="Gradient_eqtlk"] +offsets = PackedFloat32Array(0.6593674, 0.7858881) +colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_5s4aw"] +gradient = SubResource("Gradient_eqtlk") +fill = 1 +fill_from = Vector2(0.517094, 0.46581197) +fill_to = Vector2(0.08974359, 0.16239317) + +[node name="HealingEffect" type="Node2D" unique_id=1363248185] z_index = 5 script = ExtResource("1_script") -[node name="FxSprite" type="Sprite2D" parent="."] -offset = Vector2(0, -24) +[node name="FxSprite" type="Sprite2D" parent="." unique_id=972494408] texture = ExtResource("2_tex") hframes = 105 vframes = 79 frame = 589 -[node name="HealLight" type="PointLight2D" parent="."] +[node name="HealLight" type="PointLight2D" parent="." unique_id=1824746179] +enabled = false color = Color(0.3, 1, 0.35, 1) energy = 0.8 +texture = SubResource("GradientTexture2D_5s4aw") texture_scale = 0.5 -enabled = false + +[node name="SfxHeal" type="AudioStreamPlayer2D" parent="." unique_id=1867863062] +stream = ExtResource("3_5s4aw") +attenuation = 4.2870927 +panning_strength = 1.08 +bus = &"Sfx" diff --git a/src/scenes/minimap.tscn b/src/scenes/minimap.tscn new file mode 100644 index 0000000..8b1eae6 --- /dev/null +++ b/src/scenes/minimap.tscn @@ -0,0 +1,41 @@ +[gd_scene format=3 uid="uid://b5minimap01"] + +[ext_resource type="Script" path="res://scripts/minimap.gd" id="1_minimap"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bg"] +bg_color = Color(0.06, 0.06, 0.08, 0.85) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.2, 0.2, 0.25, 1) + +[node name="Minimap" type="CanvasLayer"] +layer = 198 + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -136.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = 104.0 +grow_horizontal = 0 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 + +[node name="MinimapView" type="PanelContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_bg") + +[node name="MinimapDraw" type="Control" parent="MarginContainer/MinimapView"] +custom_minimum_size = Vector2(128, 96) +layout_mode = 2 +script = ExtResource("1_minimap") diff --git a/src/scenes/player.tscn b/src/scenes/player.tscn index 042ae1f..1522b6c 100644 --- a/src/scenes/player.tscn +++ b/src/scenes/player.tscn @@ -45,6 +45,8 @@ [ext_resource type="AudioStream" uid="uid://ch3p57i7fvd1v" path="res://assets/audio/sfx/shield/shield2.wav" id="43_mx1m4"] [ext_resource type="AudioStream" uid="uid://t0sg2rxlfech" path="res://assets/audio/sfx/shield/shield3.wav" id="44_4gjji"] [ext_resource type="AudioStream" uid="uid://dvq72502qa46f" path="res://assets/audio/sfx/shield/denied_activate_Shield2.wav" id="45_g5jhy"] +[ext_resource type="AudioStream" uid="uid://8l0hx3sjh4ci" path="res://assets/audio/sfx/jsfxr/hitHurt (1).wav" id="46_holxr"] +[ext_resource type="AudioStream" uid="uid://cjv4cf2kiomwo" path="res://assets/audio/sfx/jsfxr/bird_sound.wav" id="47_mx1m4"] [sub_resource type="Gradient" id="Gradient_wqfne"] colors = PackedColorArray(0, 0, 0, 1, 1, 0.13732082, 0.092538536, 1) @@ -573,12 +575,12 @@ scale = Vector2(1.984375, 2.0937502) texture = SubResource("GradientTexture2D_wnwbv") [node name="ConeLight" type="PointLight2D" parent="." unique_id=120780131] -blend_mode = 0 +blend_mode = 2 shadow_enabled = true [node name="PointLight2D" type="PointLight2D" parent="." unique_id=1250823818] position = Vector2(-1, 0) -blend_mode = 0 +blend_mode = 2 shadow_enabled = true texture = SubResource("GradientTexture2D_f1ej7") @@ -745,7 +747,6 @@ panning_strength = 1.11 visible = false rotation = 3.1869712 energy = 0.13 -blend_mode = 0 shadow_enabled = true max_distance = 100.0 @@ -811,3 +812,16 @@ frame = 711 [node name="AnimationPlayerStatus" type="AnimationPlayer" parent="Sprite2DStatus" unique_id=721795152] libraries/ = SubResource("AnimationLibrary_mx1m4") autoplay = &"idle" + +[node name="SfxFallDownLand" type="AudioStreamPlayer2D" parent="." unique_id=1674409490] +stream = ExtResource("46_holxr") +max_distance = 1677.0 +attenuation = 1.8660662 +bus = &"Sfx" + +[node name="SfxBirdSound" type="AudioStreamPlayer2D" parent="." unique_id=1946085725] +stream = ExtResource("47_mx1m4") +volume_db = -13.255 +attenuation = 3.2490087 +panning_strength = 1.12 +bus = &"Sfx" diff --git a/src/scenes/player.tscn69243350395.tmp b/src/scenes/player.tscn69243350395.tmp new file mode 100644 index 0000000..01923d0 --- /dev/null +++ b/src/scenes/player.tscn69243350395.tmp @@ -0,0 +1,826 @@ +[gd_scene format=3 uid="uid://cxfvw8y7jqn2p"] + +[ext_resource type="Script" uid="uid://ck72vhkja7nbo" path="res://scripts/player.gd" id="1"] +[ext_resource type="Script" uid="uid://cpxabh3uq1kl4" path="res://scripts/create_shadow_sprite.gd" id="3"] +[ext_resource type="Shader" uid="uid://ldl7vaq5n13f" path="res://shaders/cloth.gdshader" id="3_wnwbv"] +[ext_resource type="Texture2D" uid="uid://bkninujaqqvb1" path="res://assets/gfx/Puny-Characters/Layer 0 - Skins/Human1_1.png" id="4"] +[ext_resource type="Texture2D" uid="uid://dx1fovugabbwc" path="res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png" id="5"] +[ext_resource type="Texture2D" uid="uid://bbqk2lcs772q3" path="res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Armour Body/BronzeArmour.png" id="6"] +[ext_resource type="Texture2D" uid="uid://bkiexfnpcaxwa" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Facial Hairstyles/Mustache1White.png" id="7"] +[ext_resource type="Texture2D" uid="uid://ccu5cpyo7jpdr" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Hairstyles/MHairstyle8White.png" id="8"] +[ext_resource type="Texture2D" uid="uid://0lmhxwt7k3e4" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eye Color/EyecolorLightLime.png" id="9"] +[ext_resource type="Texture2D" uid="uid://b4vh2v0x58v2f" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eyelashes/MEyelash1.png" id="10"] +[ext_resource type="Texture2D" uid="uid://jxo0e2x145rs" path="res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Elf Add-ons/ElfEars3.png" id="11"] +[ext_resource type="Texture2D" uid="uid://cu5fkio3ajr5i" path="res://assets/gfx/Puny-Characters/Layer 6 - Headgears/French/MusketeerHatPurple.png" id="12"] +[ext_resource type="Texture2D" uid="uid://bloqx3mibftjn" path="res://assets/gfx/Puny-Characters/WeaponOverlayer.png" id="13"] +[ext_resource type="AudioStream" uid="uid://cbio6f0ssxvd6" path="res://assets/audio/sfx/walk/stone/walk_stone_1.wav.mp3" id="13_fulsm"] +[ext_resource type="Texture2D" uid="uid://bkca7nmt4du5e" path="res://assets/gfx/Puny-Characters/ShieldOverlayer.png" id="13_t4otl"] +[ext_resource type="AudioStream" uid="uid://dq1va2882v23v" path="res://assets/audio/sfx/walk/stone/walk_stone_2.wav.mp3" id="14_4r5pv"] +[ext_resource type="Texture2D" uid="uid://bpxxpdpow5qyl" path="res://assets/gfx/Puny-Characters/ShieldOverlayerHolding.png" id="14_j2b1d"] +[ext_resource type="AudioStream" uid="uid://dsuf4oa710gi8" path="res://assets/audio/sfx/walk/stone/walk_stone_3.wav.mp3" id="15_60mlk"] +[ext_resource type="AudioStream" uid="uid://fvhvmxtcq018" path="res://assets/audio/sfx/walk/stone/walk_stone_4.wav.mp3" id="16_i4ail"] +[ext_resource type="AudioStream" uid="uid://cw74evef8fm0t" path="res://assets/audio/sfx/walk/stone/walk_stone_5.wav.mp3" id="17_a38lo"] +[ext_resource type="AudioStream" uid="uid://c43fyqtos11fd" path="res://assets/audio/sfx/walk/stone/walk_stone_6.wav.mp3" id="18_4ni07"] +[ext_resource type="AudioStream" uid="uid://b4ng0o2en2hkm" path="res://assets/audio/sfx/player/fall_out/player_fall_infinitely-02.wav.mp3" id="19_4r5pv"] +[ext_resource type="AudioStream" uid="uid://bi546r2d771yg" path="res://assets/audio/sfx/player/take_damage/player_damaged_01.wav.mp3" id="20_ujl30"] +[ext_resource type="AudioStream" uid="uid://b8trgc0pbomud" path="res://assets/audio/sfx/player/take_damage/player_damaged_02.wav.mp3" id="21_31cv2"] +[ext_resource type="AudioStream" uid="uid://dsnvagvhs152x" path="res://assets/audio/sfx/player/take_damage/player_damaged_03.wav.mp3" id="22_pf23h"] +[ext_resource type="AudioStream" uid="uid://ce51n4tvvflro" path="res://assets/audio/sfx/player/take_damage/player_damaged_04.wav.mp3" id="23_dt7fs"] +[ext_resource type="AudioStream" uid="uid://caclaiagfnr2o" path="res://assets/audio/sfx/player/take_damage/player_damaged_05.wav.mp3" id="24_wqfne"] +[ext_resource type="AudioStream" uid="uid://dighi525ty7sl" path="res://assets/audio/sfx/player/take_damage/player_damaged_06.wav.mp3" id="25_wnwbv"] +[ext_resource type="AudioStream" uid="uid://bdhmel5vyixng" path="res://assets/audio/sfx/player/take_damage/player_damaged_07.wav.mp3" id="26_gl8cc"] +[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="27_31cv2"] +[ext_resource type="AudioStream" uid="uid://w6yon88kjfml" path="res://assets/audio/sfx/nickes/lift_sfx.ogg" id="28_pf23h"] +[ext_resource type="AudioStream" uid="uid://b6klanrso0vvq" path="res://assets/audio/sfx/weapons/bow/bow_release_1.mp3" id="30_md1ol"] +[ext_resource type="AudioStream" uid="uid://mbtpqlb5n3gd" path="res://assets/audio/sfx/weapons/bow/bow_no_arrow.mp3" id="31_487ah"] +[ext_resource type="AudioStream" uid="uid://b6mwlp2ap0wbj" path="res://assets/audio/sfx/weapons/bow/bow_release2.mp3" id="31_bj30b"] +[ext_resource type="AudioStream" uid="uid://cgya50qrx8gms" path="res://assets/audio/sfx/weapons/bow/buckle_bow.mp3" id="32_gl8cc"] +[ext_resource type="AudioStream" uid="uid://d1ut5lnlch0k2" path="res://assets/audio/sfx/weapons/bow/bow_release3.mp3" id="32_jc3p3"] +[ext_resource type="AudioStream" uid="uid://bvi00vbftbgc5" path="res://assets/audio/sfx/player/ultra_run/shinespark_start.wav" id="35_bj30b"] +[ext_resource type="AudioStream" uid="uid://0xm3gyh8051h" path="res://assets/audio/sfx/wizard/incantations/indignation.mp3" id="36_jc3p3"] +[ext_resource type="Texture2D" uid="uid://bf158atxi7ucy" path="res://assets/gfx/fx/shade_spell_effects.png" id="37_hax0n"] +[ext_resource type="AudioStream" uid="uid://c10ju1f6d4ed3" path="res://assets/audio/sfx/shield/activate_shield.wav" id="40_hhpqf"] +[ext_resource type="AudioStream" uid="uid://ly1euk0v3jxy" path="res://assets/audio/sfx/shield/shield.wav" id="41_g5jhy"] +[ext_resource type="AudioStream" uid="uid://c4lh535yj010h" path="res://assets/audio/sfx/shield/shield1.wav" id="42_holxr"] +[ext_resource type="AudioStream" uid="uid://ch3p57i7fvd1v" path="res://assets/audio/sfx/shield/shield2.wav" id="43_mx1m4"] +[ext_resource type="AudioStream" uid="uid://t0sg2rxlfech" path="res://assets/audio/sfx/shield/shield3.wav" id="44_4gjji"] +[ext_resource type="AudioStream" uid="uid://dvq72502qa46f" path="res://assets/audio/sfx/shield/denied_activate_Shield2.wav" id="45_g5jhy"] +[ext_resource type="AudioStream" uid="uid://8l0hx3sjh4ci" path="res://assets/audio/sfx/jsfxr/hitHurt (1).wav" id="46_holxr"] +[ext_resource type="AudioStream" uid="uid://cjv4cf2kiomwo" path="res://assets/audio/sfx/jsfxr/bird_sound.wav" id="47_mx1m4"] + +[sub_resource type="Gradient" id="Gradient_wqfne"] +colors = PackedColorArray(0, 0, 0, 1, 1, 0.13732082, 0.092538536, 1) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_wnwbv"] +gradient = SubResource("Gradient_wqfne") +fill_from = Vector2(0.46153846, 0.87606835) +fill_to = Vector2(0.46153846, 0.11965812) + +[sub_resource type="Gradient" id="Gradient_jej6c"] +offsets = PackedFloat32Array(0.7710843, 0.77710843) +colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_f1ej7"] +gradient = SubResource("Gradient_jej6c") +use_hdr = true +fill = 1 +fill_from = Vector2(0.51304346, 0.51304346) +fill_to = Vector2(0.8974359, 0.08547009) + +[sub_resource type="CircleShape2D" id="CircleShape2D_pf23h"] +radius = 32.0 + +[sub_resource type="Gradient" id="Gradient_3v2ag"] +offsets = PackedFloat32Array(0.3883721, 0.8372093) +colors = PackedColorArray(0, 0, 0, 0.74509805, 0, 0, 0, 0) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_jej6c"] +gradient = SubResource("Gradient_3v2ag") +width = 12 +height = 6 +fill = 1 +fill_from = Vector2(0.51304346, 0.46086955) +fill_to = Vector2(0, 0) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_md1ol"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_bj30b"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_jc3p3"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_hax0n"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_t4otl"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_j2b1d"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_cs1tg"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_2dvfe"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_giy8y"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_hhpqf"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_fdfoy"] +shader = ExtResource("3_wnwbv") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + +[sub_resource type="CircleShape2D" id="CircleShape2D_wnwbv"] +radius = 4.0 + +[sub_resource type="CircleShape2D" id="CircleShape2D_2"] +radius = 8.0 + +[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_l71n6"] +playback_mode = 1 +random_pitch = 1.0118532 +streams_count = 6 +stream_0/stream = ExtResource("13_fulsm") +stream_1/stream = ExtResource("14_4r5pv") +stream_2/stream = ExtResource("15_60mlk") +stream_3/stream = ExtResource("16_i4ail") +stream_4/stream = ExtResource("17_a38lo") +stream_5/stream = ExtResource("18_4ni07") + +[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_487ah"] +playback_mode = 1 +random_pitch = 1.0118532 +streams_count = 7 +stream_0/stream = ExtResource("20_ujl30") +stream_1/stream = ExtResource("21_31cv2") +stream_2/stream = ExtResource("22_pf23h") +stream_3/stream = ExtResource("23_dt7fs") +stream_4/stream = ExtResource("24_wqfne") +stream_5/stream = ExtResource("25_wnwbv") +stream_6/stream = ExtResource("26_gl8cc") + +[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_bj30b"] +playback_mode = 1 +random_pitch = 1.0123794 +streams_count = 3 +stream_0/stream = ExtResource("30_md1ol") +stream_1/stream = ExtResource("31_bj30b") +stream_2/stream = ExtResource("32_jc3p3") + +[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_hhpqf"] +random_pitch = 1.0630184 +streams_count = 1 +stream_0/stream = ExtResource("31_487ah") + +[sub_resource type="Animation" id="Animation_t4otl"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [2037] +} + +[sub_resource type="Animation" id="Animation_j2b1d"] +resource_name = "fire_charging" +length = 0.4 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.03333334, 0.06666667, 0.10000002, 0.13333336, 0.16666669, 0.20000002, 0.23333335, 0.26666668, 0.30000004, 0.33333337, 0.3666667, 0.4, 0.43333337, 0.46666673, 0.5, 0.53333336), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, 2052, 2053] +} + +[sub_resource type="Animation" id="Animation_cs1tg"] +resource_name = "fire_ready" +length = 0.6 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.03333334, 0.06666667, 0.10000002, 0.13333336, 0.16666669, 0.20000002, 0.23333335, 0.26666668, 0.30000004, 0.33333337, 0.3666667, 0.4, 0.43333337, 0.46666673, 0.5, 0.53333336), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, 2052, 2053] +} + +[sub_resource type="Animation" id="Animation_frost_ch"] +resource_name = "frost_charging" +length = 0.566 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.03333334, 0.06666667, 0.1, 0.13333334, 0.16666667, 0.2, 0.23333333, 0.26666668, 0.3, 0.33333334, 0.36666667, 0.4, 0.43333334, 0.46666667, 0.5, 0.53333336), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [3678, 3679, 3680, 3681, 3682, 3683, 3684, 3685, 3686, 3687, 3688, 3689, 3690, 3691, 3692, 3693, 3694] +} + +[sub_resource type="Animation" id="Animation_frost_rdy"] +resource_name = "frost_ready" +length = 0.566 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.03333334, 0.06666667, 0.1, 0.13333334, 0.16666667, 0.2, 0.23333333, 0.26666668, 0.3, 0.33333334, 0.36666667, 0.4, 0.43333334, 0.46666667, 0.5, 0.53333336), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [3678, 3679, 3680, 3681, 3682, 3683, 3684, 3685, 3686, 3687, 3688, 3689, 3690, 3691, 3692, 3693, 3694] +} + +[sub_resource type="Animation" id="Animation_heal_ch"] +resource_name = "healing_charging" +length = 0.5 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [589, 590, 591, 592, 593, 594, 595, 596, 597, 598] +} + +[sub_resource type="Animation" id="Animation_heal_rdy"] +resource_name = "healing_ready" +length = 0.5 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [589, 590, 591, 592, 593, 594, 595, 596, 597, 598] +} + +[sub_resource type="Animation" id="Animation_hax0n"] +resource_name = "idle" +length = 0.1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [0] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_2dvfe"] +_data = { +&"RESET": SubResource("Animation_t4otl"), +&"fire_charging": SubResource("Animation_j2b1d"), +&"fire_ready": SubResource("Animation_cs1tg"), +&"frost_charging": SubResource("Animation_frost_ch"), +&"frost_ready": SubResource("Animation_frost_rdy"), +&"healing_charging": SubResource("Animation_heal_ch"), +&"healing_ready": SubResource("Animation_heal_rdy"), +&"idle": SubResource("Animation_hax0n") +} + +[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_u2ulf"] +streams_count = 4 +stream_0/stream = ExtResource("41_g5jhy") +stream_1/stream = ExtResource("42_holxr") +stream_2/stream = ExtResource("43_mx1m4") +stream_3/stream = ExtResource("44_4gjji") + +[sub_resource type="Animation" id="Animation_g5jhy"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [711] +} + +[sub_resource type="Animation" id="Animation_holxr"] +resource_name = "concussion" +length = 1.0666667 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.06666667, 0.13333333, 0.2, 0.26666668, 0.33333334, 0.4, 0.46666667, 0.53333336, 0.6, 0.6666667, 0.73333335, 0.8, 0.8666667, 0.93333334, 1), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726] +} + +[sub_resource type="Animation" id="Animation_u2ulf"] +resource_name = "idle" +length = 0.46666667 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [0] +} + +[sub_resource type="Animation" id="Animation_4gjji"] +resource_name = "poison" +length = 0.6 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.06666667, 0.13333334, 0.2, 0.26666668, 0.33333334, 0.4, 0.46666667, 0.53333336), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 1, +"values": [1893, 1894, 1895, 1896, 1897, 1898, 1899, 1900, 1901] +} + +[sub_resource type="Animation" id="Animation_mx1m4"] +resource_name = "sleep" +length = 0.6 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [291, 292, 293, 294, 295, 296] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_mx1m4"] +_data = { +&"RESET": SubResource("Animation_g5jhy"), +&"concussion": SubResource("Animation_holxr"), +&"idle": SubResource("Animation_u2ulf"), +&"poison": SubResource("Animation_4gjji"), +&"sleep": SubResource("Animation_mx1m4") +} + +[node name="Player" type="CharacterBody2D" unique_id=937429705] +collision_mask = 67 +motion_mode = 1 +script = ExtResource("1") + +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=720799975] +visible = false +position = Vector2(1.499999, -2.0000038) +scale = Vector2(1.984375, 2.0937502) +texture = SubResource("GradientTexture2D_wnwbv") + +[node name="ConeLight" type="PointLight2D" parent="." unique_id=120780131] +blend_mode = 2 +shadow_enabled = true + +[node name="PointLight2D" type="PointLight2D" parent="." unique_id=1250823818] +position = Vector2(-1, 0) +blend_mode = 2 +shadow_enabled = true +texture = SubResource("GradientTexture2D_f1ej7") + +[node name="LightCollision" type="Area2D" parent="PointLight2D" unique_id=502090625] +collision_layer = 0 +collision_mask = 16384 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="PointLight2D/LightCollision" unique_id=1350075834] +shape = SubResource("CircleShape2D_pf23h") + +[node name="Shadow" type="Sprite2D" parent="." unique_id=937683521] +position = Vector2(0, 7) +texture = SubResource("GradientTexture2D_jej6c") +script = ExtResource("3") + +[node name="Sprite2DBody" type="Sprite2D" parent="." unique_id=2113577699] +y_sort_enabled = true +material = SubResource("ShaderMaterial_md1ol") +texture = ExtResource("4") +hframes = 35 +vframes = 8 + +[node name="Sprite2DBoots" type="Sprite2D" parent="." unique_id=598174931] +y_sort_enabled = true +material = SubResource("ShaderMaterial_bj30b") +texture = ExtResource("5") +hframes = 35 +vframes = 8 + +[node name="Sprite2DArmour" type="Sprite2D" parent="." unique_id=2130297502] +y_sort_enabled = true +material = SubResource("ShaderMaterial_jc3p3") +texture = ExtResource("6") +hframes = 35 +vframes = 8 + +[node name="Sprite2DFacialHair" type="Sprite2D" parent="." unique_id=1050766722] +y_sort_enabled = true +material = SubResource("ShaderMaterial_hax0n") +texture = ExtResource("7") +hframes = 35 +vframes = 8 + +[node name="Sprite2DHair" type="Sprite2D" parent="." unique_id=927492041] +y_sort_enabled = true +material = SubResource("ShaderMaterial_t4otl") +texture = ExtResource("8") +hframes = 35 +vframes = 8 + +[node name="Sprite2DEyes" type="Sprite2D" parent="." unique_id=2054421939] +y_sort_enabled = true +material = SubResource("ShaderMaterial_j2b1d") +texture = ExtResource("9") +hframes = 35 +vframes = 8 + +[node name="Sprite2DEyeLashes" type="Sprite2D" parent="." unique_id=1437938522] +y_sort_enabled = true +material = SubResource("ShaderMaterial_cs1tg") +texture = ExtResource("10") +hframes = 35 +vframes = 8 + +[node name="Sprite2DAddons" type="Sprite2D" parent="." unique_id=962307958] +y_sort_enabled = true +material = SubResource("ShaderMaterial_2dvfe") +texture = ExtResource("11") +hframes = 35 +vframes = 8 + +[node name="Sprite2DHeadgear" type="Sprite2D" parent="." unique_id=526463008] +y_sort_enabled = true +material = SubResource("ShaderMaterial_giy8y") +texture = ExtResource("12") +hframes = 35 +vframes = 8 + +[node name="Sprite2DShield" type="Sprite2D" parent="." unique_id=738217548] +material = SubResource("ShaderMaterial_hhpqf") +texture = ExtResource("13_t4otl") +hframes = 35 +vframes = 8 + +[node name="Sprite2DShieldHolding" type="Sprite2D" parent="." unique_id=1000811066] +material = SubResource("ShaderMaterial_hhpqf") +texture = ExtResource("14_j2b1d") +hframes = 35 +vframes = 8 + +[node name="Sprite2DWeapon" type="Sprite2D" parent="." unique_id=1889932388] +z_index = 1 +y_sort_enabled = true +material = SubResource("ShaderMaterial_fdfoy") +texture = ExtResource("13") +hframes = 35 +vframes = 8 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=989315141] +position = Vector2(0, 4) +rotation = -1.5707964 +shape = SubResource("CircleShape2D_wnwbv") + +[node name="GrabArea" type="Area2D" parent="." unique_id=518653365] +position = Vector2(0, 4) +collision_layer = 0 +collision_mask = 3 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="GrabArea" unique_id=432299400] +shape = SubResource("CircleShape2D_2") +debug_color = Color(0.70196074, 0.6126261, 0.19635464, 0.41960785) + +[node name="InteractionIndicator" type="Sprite2D" parent="." unique_id=1661043470] +visible = false +modulate = Color(1, 1, 0, 0.5) +position = Vector2(0, -12) +scale = Vector2(4, 4) + +[node name="Label" type="Label" parent="." unique_id=227628720] +offset_left = -10.0 +offset_top = -15.0 +offset_right = 10.0 +offset_bottom = -9.0 +horizontal_alignment = 1 + +[node name="SfxWalk" type="AudioStreamPlayer2D" parent="." unique_id=1693322702] +stream = SubResource("AudioStreamRandomizer_l71n6") +volume_db = -18.527 +max_distance = 1412.0 +attenuation = 8.282109 +panning_strength = 1.11 + +[node name="TimerWalk" type="Timer" parent="SfxWalk" unique_id=590325386] +wait_time = 0.3 +one_shot = true + +[node name="SfxDie" type="AudioStreamPlayer2D" parent="." unique_id=1173215688] +stream = ExtResource("19_4r5pv") +volume_db = -2.537 +attenuation = 8.876548 +bus = &"Sfx" + +[node name="SfxTakeDamage" type="AudioStreamPlayer2D" parent="." unique_id=322150091] +stream = SubResource("AudioStreamRandomizer_487ah") +volume_db = -6.092 +attenuation = 7.7274756 +panning_strength = 1.1 +bus = &"Sfx" + +[node name="SfxThrow" type="AudioStreamPlayer2D" parent="." unique_id=961008127] +stream = ExtResource("27_31cv2") +pitch_scale = 0.61 +max_distance = 983.0 +attenuation = 8.876549 +panning_strength = 1.04 + +[node name="SfxLift" type="AudioStreamPlayer2D" parent="." unique_id=1261167113] +stream = ExtResource("28_pf23h") +max_distance = 1246.0 +attenuation = 6.964403 +panning_strength = 1.11 + +[node name="DirectionalLight2D" type="DirectionalLight2D" parent="." unique_id=1013099358] +visible = false +rotation = 3.1869712 +energy = 0.13 +shadow_enabled = true +max_distance = 100.0 + +[node name="SfxBowShoot" type="AudioStreamPlayer2D" parent="." unique_id=340970961] +stream = SubResource("AudioStreamRandomizer_bj30b") +pitch_scale = 1.33 +attenuation = 6.7271657 + +[node name="SfxBowWithoutArrow" type="AudioStreamPlayer2D" parent="." unique_id=189976587] +stream = SubResource("AudioStreamRandomizer_hhpqf") +max_distance = 1455.0 +attenuation = 7.4642572 + +[node name="SfxBuckleBow" type="AudioStreamPlayer2D" parent="." unique_id=1991119205] +stream = ExtResource("32_gl8cc") +attenuation = 7.727478 +panning_strength = 1.03 + +[node name="SfxSpellCharge" type="AudioStreamPlayer2D" parent="." unique_id=282455991] +stream = ExtResource("35_bj30b") + +[node name="SfxSpellIncantation" type="AudioStreamPlayer2D" parent="." unique_id=300820616] +stream = ExtResource("36_jc3p3") +volume_db = -46.271 +attenuation = 7.727487 +panning_strength = 1.04 +bus = &"Sfx" + +[node name="IncantationSprite" type="Sprite2D" parent="." unique_id=1655944614] +texture = ExtResource("37_hax0n") +hframes = 105 +vframes = 79 +frame = 2037 + +[node name="AnimationIncantation" type="AnimationPlayer" parent="." unique_id=17658820] +libraries/ = SubResource("AnimationLibrary_2dvfe") +autoplay = &"idle" + +[node name="SfxActivateShield" type="AudioStreamPlayer2D" parent="." unique_id=1247414383] +stream = ExtResource("40_hhpqf") +volume_db = 9.695 +attenuation = 1.3660401 +panning_strength = 1.78 + +[node name="SfxBlockWithShield" type="AudioStreamPlayer2D" parent="." unique_id=1010944313] +stream = SubResource("AudioStreamRandomizer_u2ulf") +volume_db = 7.254 +attenuation = 1.3195078 +panning_strength = 1.06 +bus = &"Sfx" + +[node name="SfxDenyActivateShield" type="AudioStreamPlayer2D" parent="." unique_id=1239738371] +stream = ExtResource("45_g5jhy") +volume_db = 9.458 + +[node name="Sprite2DStatus" type="Sprite2D" parent="." unique_id=1335748461] +position = Vector2(0, -10) +texture = ExtResource("37_hax0n") +hframes = 105 +vframes = 79 +frame = 711 + +[node name="AnimationPlayerStatus" type="AnimationPlayer" parent="Sprite2DStatus" unique_id=721795152] +libraries/ = SubResource("AnimationLibrary_mx1m4") +autoplay = &"idle" + +[node name="SfxFallDownLand" type="AudioStreamPlayer2D" parent="." unique_id=1674409490] +stream = ExtResource("46_holxr") +attenuation = 1.8025011 +bus = &"Sfx" + +[node name="SfxBirdSound" type="AudioStreamPlayer2D" parent="." unique_id=1946085725] +stream = ExtResource("47_mx1m4") +volume_db = -13.255 +attenuation = 3.2490087 +panning_strength = 1.12 +bus = &"Sfx" diff --git a/src/scripts/attack_arrow.gd b/src/scripts/attack_arrow.gd index 1c4f8d6..4d0ad40 100644 --- a/src/scripts/attack_arrow.gd +++ b/src/scripts/attack_arrow.gd @@ -456,7 +456,11 @@ func _on_collection_area_body_entered(body: Node2D): print("ERROR: character_stats missing add_item method") return + var was_encumbered = body.character_stats.is_over_encumbered() body.character_stats.add_item(inventory_arrow) + if not was_encumbered and body.character_stats.is_over_encumbered(): + if body.has_method("show_floating_status"): + body.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2)) is_collected = true $SfxPickup.play() print(body.name, " collected arrow from wall into inventory!") diff --git a/src/scripts/attack_spell_frostspike.gd b/src/scripts/attack_spell_frostspike.gd index 8bd6031..2c22f30 100644 --- a/src/scripts/attack_spell_frostspike.gd +++ b/src/scripts/attack_spell_frostspike.gd @@ -7,6 +7,7 @@ var player_owner: Node = null var damage: float = 15.0 var damage_mult: float = 1.0 var is_center: bool = false +var skip_sfx: bool = false var damage_dealt: bool = false var elapsed: float = 0.0 var _frames: Array = [4413, 4414, 4415, 4416] @@ -23,15 +24,18 @@ func _ready() -> void: if sprite: sprite.frame = _frames[0] sprite.modulate = Color(0.6, 0.8, 1.0) + if not skip_sfx and has_node("SfxSpike"): + $SfxSpike.play() if is_center: _spawn_adjacent_after_delay() -func setup(target_pos: Vector2, owner_player: Node, damage_value: float, center: bool, scale_mult: float = 1.0, dmg_mult: float = 1.0) -> void: +func setup(target_pos: Vector2, owner_player: Node, damage_value: float, center: bool, scale_mult: float = 1.0, dmg_mult: float = 1.0, _skip_sfx: bool = false) -> void: global_position = target_pos player_owner = owner_player damage = damage_value damage_mult = dmg_mult is_center = center + skip_sfx = _skip_sfx if scale_mult != 1.0: scale = Vector2(scale_mult, scale_mult) @@ -52,12 +56,14 @@ func _spawn_adjacent_after_delay() -> void: var par = get_parent() for pos in adjacent: var sp = scene.instantiate() - sp.setup(pos, player_owner, damage, false) + sp.setup(pos, player_owner, damage, false, 1.0, 1.0, true) par.add_child(sp) # Third wave: center again, 2x scale, 2x damage (most damage) var third = scene.instantiate() - third.setup(global_position, player_owner, damage, false, 2.0, 2.0) + third.setup(global_position, player_owner, damage, false, 2.0, 2.0, true) par.add_child(third) + if has_node("SfxSpike"): + $SfxSpike.play() _finish_center_spike() func _finish_center_spike() -> void: diff --git a/src/scripts/character_stats.gd b/src/scripts/character_stats.gd index a5aac90..f8aa17d 100644 --- a/src/scripts/character_stats.gd +++ b/src/scripts/character_stats.gd @@ -41,6 +41,11 @@ var bonusmaxmp: float = 0.0 var kills: int = 0 var deaths: int = 0 +# Level-up stat allocation: when true, player must open inventory and choose a stat to +1 +var pending_level_up: bool = false +var pending_stat_points: int = 0 +const LEVEL_UP_STAT_NAMES: Array = ["str", "dex", "int", "end", "wis", "lck", "per"] + #calculated values var stats:Array = [ 5, @@ -236,49 +241,46 @@ func _init() -> void: func add_xp(amount: float) -> void: xp += amount + xp = max(0.0, xp) xp_changed.emit(xp, xp_to_next_level) while xp >= xp_to_next_level: level_up() + xp = max(0.0, xp) + xp_changed.emit(xp, xp_to_next_level) -# Level up - randomly increases 2-3 stats instead of all +# Level up - grant pending stat point(s); player allocates in inventory func level_up() -> void: + # Subtract threshold for current level *before* incrementing (xp_to_next_level uses level) + var xp_threshold = xp_to_next_level level += 1 - xp -= xp_to_next_level + xp -= xp_threshold + xp = max(0.0, xp) - # Randomly select 2-3 stats to increase (random 2 or 3) - var num_stats_to_increase = randi_range(2, 3) - - # All available stats (excluding cha for now as per user request) - var available_stats = ["str", "dex", "int", "end", "wis", "lck", "per"] - - # Shuffle and pick random stats - var stats_to_increase = [] - var shuffled_stats = available_stats.duplicate() - shuffled_stats.shuffle() - - for i in range(min(num_stats_to_increase, shuffled_stats.size())): - stats_to_increase.append(shuffled_stats[i]) - - # Increase selected stats - for stat_name in stats_to_increase: - baseStats[stat_name] += 1 - - # Store which stats were increased (for display) - var stats_increased = stats_to_increase + pending_level_up = true + pending_stat_points += 1 # Restore health and mana on level up hp = maxhp mp = maxmp - # Emit level_changed signal level_changed.emit(level) - # Emit level_up_stats signal with which stats were increased - level_up_stats.emit(stats_increased) - + level_up_stats.emit([]) health_changed.emit(hp, maxhp) mana_changed.emit(mp, maxmp) +func allocate_stat_point(stat_name: String) -> bool: + if not pending_level_up or pending_stat_points <= 0: + return false + if stat_name not in LEVEL_UP_STAT_NAMES: + return false + baseStats[stat_name] += 1 + pending_stat_points -= 1 + if pending_stat_points <= 0: + pending_level_up = false + character_changed.emit(self) + return true + func modify_health(amount: float, allow_overheal: bool = false) -> void: hp += amount if allow_overheal: @@ -388,6 +390,8 @@ func save() -> Dictionary: "kills": kills, "deaths": deaths, + "pending_level_up": pending_level_up, + "pending_stat_points": pending_stat_points, "skin": skin, "facial_hair": facial_hair, @@ -427,11 +431,15 @@ func load(iDic: Dictionary) -> void: if iDic.has("coin"): coin = iDic.get("coin") if iDic.has("exp"): - xp = iDic.get("exp") + xp = max(0.0, float(iDic.get("exp"))) if iDic.has("kills"): kills = iDic.get("kills") if iDic.has("deaths"): deaths = iDic.get("deaths") + if iDic.has("pending_level_up"): + pending_level_up = bool(iDic.get("pending_level_up")) + if iDic.has("pending_stat_points"): + pending_stat_points = int(iDic.get("pending_stat_points")) inventory.clear() if iDic.has("inventory"): diff --git a/src/scripts/damage_number.gd b/src/scripts/damage_number.gd index 370334b..f8ff393 100644 --- a/src/scripts/damage_number.gd +++ b/src/scripts/damage_number.gd @@ -16,6 +16,18 @@ func _ready() -> void: pass # Replace with function body. func _initialize_damage_number() -> void: + # Reparent to FloatingTextLayer (not affected by CanvasModulate). + # Layer has follow_viewport_enabled = true so it scrolls with world → use world position, not screen. + var gw = get_tree().get_first_node_in_group("game_world") + var layer = gw.get_node_or_null("FloatingTextLayer") if gw else null + if layer and gw: + var world_pos = global_position + get_parent().remove_child(self) + layer.add_child(self) + global_position = world_pos + # Ensure visible: use default layer so camera/viewport always draws us (fixes layer 262144 not being shown) + visibility_layer = 1 + light_mask = 1 # Set color (red by default) and text self.modulate = color self.text = label @@ -25,7 +37,7 @@ func _initialize_damage_number() -> void: var random_angle = randf_range(-PI/6, PI/6) # ±30 degrees from straight up direction = Vector2(sin(random_angle), -cos(random_angle)) # Mostly upward with slight variation - # Calculate target position (move upward with slight horizontal variation) + # Calculate target position (move upward with slight horizontal variation; in screen space if on layer) var move_target = global_position + (direction.normalized() * rise_distance) # Total animation duration = display (0.6s) + fade (0.4s) = 1.0s diff --git a/src/scripts/enemy_base.gd b/src/scripts/enemy_base.gd index 22762d7..fa509b9 100644 --- a/src/scripts/enemy_base.gd +++ b/src/scripts/enemy_base.gd @@ -11,6 +11,7 @@ extends CharacterBody2D var current_health: float = 50.0 var character_stats: CharacterStats # RPG stats system (same as players) var is_dead: bool = false +@export var is_undead: bool = false # Zombies etc.; healing spell damages them var target_player: Node = null var attack_timer: float = 0.0 var killer_player: Node = null # Track who killed this enemy (for kill credit) diff --git a/src/scripts/enemy_hand.gd b/src/scripts/enemy_hand.gd new file mode 100644 index 0000000..418112c --- /dev/null +++ b/src/scripts/enemy_hand.gd @@ -0,0 +1,214 @@ +extends "res://scripts/enemy_base.gd" + +# Enemy Hand: hidden until player near (EmergeArea), then emerge -> idle. +# Moves toward player if in PlayerInterestArea, else random. Collides with walls. +# If player enters GrabPlayerArea and alive: grab, lock player, snatch anim, deal damage, release + knockback. + +enum HandState { HIDDEN, EMERGING, IDLE, GRABBING } + +var state: HandState = HandState.HIDDEN +var players_in_interest: Array[Node] = [] +var grabbed_player: Node = null +var random_move_dir: Vector2 = Vector2.ZERO +var random_move_timer: float = 0.0 +const RANDOM_MOVE_INTERVAL: float = 1.2 +const SNATCH_DURATION: float = 0.4 +const SNATCH_DAMAGE: float = 12.0 +@onready var emerge_area: Area2D = $EmergeArea +@onready var grab_area: Area2D = $GrabPlayerArea +@onready var interest_area: Area2D = $PlayerInterestArea +@onready var anim_player: AnimationPlayer = $AnimationPlayer + + +func _ready() -> void: + super._ready() + max_health = 25.0 + current_health = max_health + move_speed = 55.0 + damage = SNATCH_DAMAGE + exp_reward = 8.0 + collision_layer = 2 + collision_mask = 1 | 2 | 64 + + # Start hidden + modulate.a = 0.0 + # Areas detect players (layer 1) + for area in [emerge_area, grab_area, interest_area]: + if area: + area.set_collision_mask_value(1, true) + area.monitoring = true + area.monitorable = false + + if anim_player: + if not anim_player.animation_finished.is_connected(_on_animation_finished): + anim_player.animation_finished.connect(_on_animation_finished) + + +func _initialize_character_stats() -> void: + super._initialize_character_stats() + character_stats.baseStats.str = 10 + character_stats.baseStats.end = 6 + character_stats.baseStats.dex = 8 + character_stats.hp = character_stats.maxhp + character_stats.mp = character_stats.maxmp + max_health = character_stats.maxhp + current_health = character_stats.hp + + +func _check_player_collision() -> void: + # No contact-based attack; we use GrabPlayerArea instead + pass + + +func _die() -> void: + if grabbed_player != null and is_instance_valid(grabbed_player): + var v = grabbed_player + grabbed_player = null + if v.has_method("rpc_released_from_enemy_hand"): + var pid = v.get_multiplayer_authority() + if multiplayer.get_unique_id() == pid: + v.rpc_released_from_enemy_hand() + elif pid != 0: + v.rpc_released_from_enemy_hand.rpc_id(pid) + else: + v.rpc_released_from_enemy_hand.rpc() + super._die() + + +func _ai_behavior(delta: float) -> void: + if state == HandState.HIDDEN or state == HandState.EMERGING: + velocity = Vector2.ZERO + return + if state == HandState.GRABBING: + velocity = Vector2.ZERO + return + + # IDLE: move toward player if in interest, else random + var target: Node = null + for p in players_in_interest: + if is_instance_valid(p) and p.is_in_group("player") and (not "is_dead" in p or not p.is_dead): + target = p + break + + if target: + var dir = (target.global_position - global_position).normalized() + velocity = dir * move_speed + else: + # Random movement + random_move_timer -= delta + if random_move_timer <= 0.0: + random_move_timer = RANDOM_MOVE_INTERVAL + var ang = randf() * TAU + random_move_dir = Vector2(cos(ang), sin(ang)) + velocity = random_move_dir * move_speed + + move_and_slide() + + +func _on_emerge_area_body_entered(body: Node2D) -> void: + if state != HandState.HIDDEN: + return + if not body.is_in_group("player"): + return + if "is_dead" in body and body.is_dead: + return + if not is_multiplayer_authority(): + return + + state = HandState.EMERGING + modulate.a = 1.0 + if anim_player and anim_player.has_animation("emerge"): + anim_player.play("emerge") + + +func _on_animation_finished(anim_name: StringName) -> void: + if anim_name == "emerge": + state = HandState.IDLE + if anim_player and anim_player.has_animation("idle"): + anim_player.play("idle") + # snatch completion driven by timer (SNATCH_DURATION), not animation_finished + + +func _on_player_interest_area_body_entered(body: Node2D) -> void: + if not body.is_in_group("player"): + return + if body in players_in_interest: + return + players_in_interest.append(body) + + +func _on_player_interest_area_body_exited(body: Node2D) -> void: + players_in_interest.erase(body) + + +func _on_grab_player_area_body_entered(body: Node2D) -> void: + if state != HandState.IDLE or is_dead: + return + if not body.is_in_group("player"): + return + if "is_dead" in body and body.is_dead: + return + if "grabbed_by_enemy_hand" in body and body.grabbed_by_enemy_hand != null: + return + if grabbed_player != null: + return + if not is_multiplayer_authority(): + return + + grabbed_player = body + state = HandState.GRABBING + velocity = Vector2.ZERO + + # Lock player movement via RPC (victim's authority runs their physics) + if body.has_method("rpc_grabbed_by_enemy_hand"): + var pid = body.get_multiplayer_authority() + if multiplayer.get_unique_id() == pid: + body.rpc_grabbed_by_enemy_hand(name) + elif pid != 0: + body.rpc_grabbed_by_enemy_hand.rpc_id(pid, name) + else: + body.rpc_grabbed_by_enemy_hand.rpc(name) + + if anim_player and anim_player.has_animation("snatch"): + anim_player.play("snatch") + get_tree().create_timer(SNATCH_DURATION).timeout.connect(_finish_snatch) + + +func _finish_snatch() -> void: + if not is_instance_valid(self): + return + var victim = grabbed_player + grabbed_player = null + state = HandState.IDLE + if anim_player and anim_player.has_animation("idle"): + anim_player.play("idle") + if not is_instance_valid(victim): + return + + # Deal damage (applies knockback via take_damage) + var attack_pos = global_position + if victim.has_method("rpc_take_damage"): + var pid = victim.get_multiplayer_authority() + if pid != 0: + if multiplayer.get_unique_id() == pid: + victim.take_damage(SNATCH_DAMAGE, attack_pos, false, false) + else: + victim.rpc_take_damage.rpc_id(pid, SNATCH_DAMAGE, attack_pos, false, false) + else: + victim.rpc_take_damage.rpc(SNATCH_DAMAGE, attack_pos, false, false) + + # Release player (RPC so victim's authority clears grabbed_by_enemy_hand) + if victim.has_method("rpc_released_from_enemy_hand"): + var pid = victim.get_multiplayer_authority() + if multiplayer.get_unique_id() == pid: + victim.rpc_released_from_enemy_hand() + elif pid != 0: + victim.rpc_released_from_enemy_hand.rpc_id(pid) + else: + victim.rpc_released_from_enemy_hand.rpc() + + +func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0, frame: int = 0, anim: String = "", frame_num: int = 0, state_value: int = -1) -> void: + super._sync_position(pos, vel, z_pos, dir, frame, anim, frame_num, state_value) + # Client: keep AnimationPlayer in sync with state if we add state sync later + pass diff --git a/src/scripts/enemy_hand.gd.uid b/src/scripts/enemy_hand.gd.uid new file mode 100644 index 0000000..5ee548f --- /dev/null +++ b/src/scripts/enemy_hand.gd.uid @@ -0,0 +1 @@ +uid://bkeyokaahe2fd diff --git a/src/scripts/enemy_humanoid.gd b/src/scripts/enemy_humanoid.gd index b03ed33..f0dfd8d 100644 --- a/src/scripts/enemy_humanoid.gd +++ b/src/scripts/enemy_humanoid.gd @@ -213,6 +213,9 @@ func _ready(): # Set up stats based on type _setup_stats() + # Undead types (e.g. skeleton) take damage from healing spell + is_undead = (humanoid_type == HumanoidType.SKELETON) + # Start in idle state ai_state = AIState.IDLE state_timer = 2.0 diff --git a/src/scripts/floating_text.gd b/src/scripts/floating_text.gd index 6128fb2..1308a26 100644 --- a/src/scripts/floating_text.gd +++ b/src/scripts/floating_text.gd @@ -12,7 +12,7 @@ var fade_duration: float = 0.5 # How long fade out takes var rise_distance: float = 30.0 var is_coin: bool = false # Track if this is a coin (to animate) -func setup(text_value: String, text_color: Color, show_time: float = 0.5, fade_time: float = 0.5, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0): +func setup(text_value: String, text_color: Color, show_time: float = 0.5, fade_time: float = 0.5, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0, item = null): text = text_value color = text_color display_duration = show_time @@ -39,7 +39,9 @@ func setup(text_value: String, text_color: Color, show_time: float = 0.5, fade_t item_sprite.position = Vector2(0, -12) # Just above the text (sprite is ~16px tall) else: item_sprite.position = Vector2(0, 0) - + # Apply item colorization (cloth shader + colorReplacements) when we have an item + if item and ItemDatabase: + ItemDatabase.apply_item_colors_to_sprite(item_sprite, item) # Check if this is a coin (6 frames horizontal, 1 frame vertical) - animate it if sprite_hframes == 6 and sprite_vframes == 1: is_coin = true @@ -50,6 +52,14 @@ func setup(text_value: String, text_color: Color, show_time: float = 0.5, fade_t is_coin = false func _ready(): + # Reparent to FloatingTextLayer (not affected by CanvasModulate) and use screen position + var gw = get_tree().get_first_node_in_group("game_world") + var layer = gw.get_node_or_null("FloatingTextLayer") if gw else null + if layer and gw and gw.has_method("world_to_screen"): + var world_pos = global_position + get_parent().remove_child(self) + layer.add_child(self) + global_position = gw.world_to_screen(world_pos) # Start coin animation if needed if is_coin and item_sprite: _animate_coin() diff --git a/src/scripts/game_world.gd b/src/scripts/game_world.gd index 280bd26..a8d5f1b 100644 --- a/src/scripts/game_world.gd +++ b/src/scripts/game_world.gd @@ -157,6 +157,9 @@ func _ready(): # Create inventory UI _create_inventory_ui() + # Layer for damage numbers / floating text (not affected by CanvasModulate) + _load_floating_text_layer() + # Initialize mouse cursor system _init_mouse_cursor() @@ -1136,14 +1139,33 @@ func _show_loot_floating_text(player: Node, text: String, color: Color, item_tex floating_text.global_position = Vector2(player.global_position.x, player.global_position.y - 20) floating_text.setup(text, color, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame) -func _apply_heal_spell_sync(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false): +func _apply_heal_spell_sync(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false, is_damage_to_enemy: bool = false): var target: Node = null - for p in get_tree().get_nodes_in_group("player"): - if p.name == target_name and is_instance_valid(p): - target = p - break + if is_damage_to_enemy: + for e in get_tree().get_nodes_in_group("enemy"): + if e.name == target_name and is_instance_valid(e): + target = e + break + else: + for p in get_tree().get_nodes_in_group("player"): + if p.name == target_name and is_instance_valid(p): + target = p + break if not target: return + if is_damage_to_enemy: + # Damage already applied by caster; just spawn effect for other clients + var entities = get_node_or_null("Entities") + var parent = entities if entities else target.get_parent() + if parent: + var eff_scene = load("res://scenes/healing_effect.tscn") as PackedScene + if eff_scene: + var eff = eff_scene.instantiate() + parent.add_child(eff) + eff.global_position = target.global_position + if eff.has_method("setup"): + eff.setup(target) + return var me = multiplayer.get_unique_id() var tid = target.get_multiplayer_authority() if is_revive: @@ -2263,12 +2285,17 @@ func _update_fog_of_war(delta: float) -> void: if not current_room.is_empty(): _mark_room_explored(current_room) _mark_room_visible(current_room) + # Only hide other rooms; keep corridor visible when we can see into it (no closed door in way) for y in range(map_size.y): for x in range(map_size.x): - if not _is_tile_in_room_or_walls(Vector2i(x, y), current_room): - var idx = x + y * map_size.x - if idx >= 0 and idx < combined_seen.size(): - combined_seen[idx] = 0 + var idx = x + y * map_size.x + if idx < 0 or idx >= combined_seen.size(): + continue + var tile_room = _find_room_at_tile(Vector2i(x, y)) + if tile_room.is_empty(): + continue # Corridor: keep (already revealed by raycast if visible) + if tile_room.x != current_room.x or tile_room.y != current_room.y or tile_room.w != current_room.w or tile_room.h != current_room.h: + combined_seen[idx] = 0 # Other room: hide else: # In corridors (no room), only show tiles connected to the corridor component # AND explicitly clear combined_seen for all tiles in rooms that aren't connected @@ -2344,6 +2371,29 @@ func _update_fog_of_war(delta: float) -> void: fog_node.set_maps(explored_map, combined_seen) if fog_node.has_method("set_debug_lines"): fog_node.set_debug_lines(fog_debug_lines, FOG_DEBUG_DRAW) + var player_tile := Vector2i(-1, -1) + if local_player_list.size() > 0 and local_player_list[0]: + player_tile = Vector2i(int(local_player_list[0].global_position.x / FOG_TILE_SIZE), int(local_player_list[0].global_position.y / FOG_TILE_SIZE)) + var minimap_draw = get_node_or_null("Minimap/MarginContainer/MinimapView/MinimapDraw") + if minimap_draw and minimap_draw.has_method("set_maps") and dungeon_data.has("grid"): + var exit_tile := Vector2i(-1, -1) + var exit_discovered := false + if dungeon_data.has("stairs") and not dungeon_data.stairs.is_empty(): + var s = dungeon_data.stairs + if s.has("x") and s.has("y") and s.has("w") and s.has("h"): + exit_tile = Vector2i(s.x + s.w / 2, s.y + s.h / 2) + for dx in range(s.w): + for dy in range(s.h): + var tx := s.x + dx + var ty := s.y + dy + if tx >= 0 and ty >= 0 and tx < map_size.x and ty < map_size.y: + var idx := tx + ty * map_size.x + if idx >= 0 and idx < explored_map.size() and explored_map[idx] != 0: + exit_discovered = true + break + if exit_discovered: + break + minimap_draw.set_maps(explored_map, map_size, dungeon_data.grid, player_tile, exit_tile, exit_discovered) func _create_seen_array(map_size: Vector2i) -> PackedInt32Array: var size = map_size.x * map_size.y @@ -2391,9 +2441,7 @@ func _update_seen_for_player(player: Node, seen_map: PackedInt32Array) -> void: last_tile = tile if tile.x < 0 or tile.y < 0 or tile.x >= map_size.x or tile.y >= map_size.y: break - # If player is inside a room, don't reveal tiles outside that room (including walls) - if not current_room.is_empty() and not _is_tile_in_room_or_walls(tile, current_room): - break + # If player is in a room: allow seeing into corridor through open doorways (ray stops at wall/closed door only) # If player is in corridor, only allow seeing tiles in corridor or connected rooms if current_room.is_empty() and allowed_rooms_for_corridor != null: var tile_room = _find_room_at_tile(tile) @@ -6864,6 +6912,35 @@ func _load_hud(): print("GameWorld: HUD is_inside_tree: ", hud.is_inside_tree()) _load_off_screen_indicators() + _load_minimap() + _load_floating_text_layer() + +func _load_floating_text_layer() -> void: + var existing = get_node_or_null("FloatingTextLayer") + if existing and is_instance_valid(existing): + return + var layer = CanvasLayer.new() + layer.name = "FloatingTextLayer" + layer.layer = 210 + layer.follow_viewport_enabled = true + add_child(layer) + +func world_to_screen(world_pos: Vector2) -> Vector2: + var vp = get_viewport() + if not vp: + return world_pos + return vp.get_canvas_transform() * world_pos + +func _load_minimap() -> void: + var existing = get_node_or_null("Minimap") + if existing and is_instance_valid(existing): + return + var scene = load("res://scenes/minimap.tscn") as PackedScene + if not scene: + return + var layer = scene.instantiate() + layer.name = "Minimap" + add_child(layer) func _load_off_screen_indicators(): var existing = get_node_or_null("OffScreenIndicators") diff --git a/src/scripts/healing_effect.gd b/src/scripts/healing_effect.gd index 3f05fbd..db616d9 100644 --- a/src/scripts/healing_effect.gd +++ b/src/scripts/healing_effect.gd @@ -23,6 +23,7 @@ func setup(p_target: Node) -> void: if not target_player or not is_instance_valid(target_player): queue_free() return + $SfxHeal.play() global_position = target_player.global_position elapsed = 0.0 if fx_sprite and _frames.size() > 0: diff --git a/src/scripts/ingame_hud.gd b/src/scripts/ingame_hud.gd index 4b9c994..9652578 100644 --- a/src/scripts/ingame_hud.gd +++ b/src/scripts/ingame_hud.gd @@ -4,6 +4,10 @@ extends CanvasLayer var label_life: Label = null var texture_progress_bar_hp: TextureProgressBar = null +var label_hp_value: Label = null +var vbox_mp: VBoxContainer = null +var progress_bar_mp: ProgressBar = null +var label_mp_value: Label = null var label_keys: Label = null var label_keys_value: Label = null var label_level: Label = null @@ -107,6 +111,9 @@ func _ready(): # Find local player (with retry limit) player_search_attempts = 0 _find_local_player() + + # Add HP value label (curr/max, like inventory) and MP bar + _setup_hp_mp_ui() func _on_player_connected(_peer_id: int, _player_info: Dictionary): _update_host_info() @@ -194,9 +201,10 @@ func _process(_delta): if not label_time_value: return # Nodes not initialized yet, skip updates - # Update player health + # Update player health and MP if local_player and is_instance_valid(local_player): _update_player_health() + _update_player_mp() _update_keys_display() # Update level display @@ -219,6 +227,58 @@ func _update_hud_scale(): scale_factor = 1 scale = Vector2.ONE * scale_factor +func _setup_hp_mp_ui() -> void: + var life_vbox = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerLIFE") + var hbox = get_node_or_null("UpperLeft/HBoxContainer") + if not life_vbox or not hbox: + return + # HP value label (curr/max, like inventory) + label_hp_value = Label.new() + label_hp_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var fr = load("res://assets/fonts/standard_font.png") + if fr: + label_hp_value.add_theme_font_override("font", fr) + label_hp_value.add_theme_font_size_override("font_size", 10) + label_hp_value.text = "0/0" + life_vbox.add_child(label_hp_value) + # MP VBox (label + bar + value) + vbox_mp = VBoxContainer.new() + vbox_mp.name = "VBoxContainerMP" + vbox_mp.size_flags_horizontal = Control.SIZE_EXPAND_FILL + var mp_title = Label.new() + mp_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var fr = load("res://assets/fonts/standard_font.png") + if fr: + mp_title.add_theme_font_override("font", fr) + mp_title.add_theme_font_size_override("font_size", 10) + mp_title.text = "MP" + vbox_mp.add_child(mp_title) + progress_bar_mp = ProgressBar.new() + progress_bar_mp.custom_minimum_size = Vector2(100, 12) + progress_bar_mp.show_percentage = false + var bg = StyleBoxFlat.new() + bg.bg_color = Color(0.2, 0.2, 0.2, 0.8) + bg.set_border_width_all(1) + bg.border_color = Color(0.4, 0.4, 0.4) + progress_bar_mp.add_theme_stylebox_override("background", bg) + var fill = StyleBoxFlat.new() + fill.bg_color = Color(0.25, 0.45, 0.9) + progress_bar_mp.add_theme_stylebox_override("fill", fill) + vbox_mp.add_child(progress_bar_mp) + label_mp_value = Label.new() + label_mp_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var fr = load("res://assets/fonts/standard_font.png") + if fr: + label_mp_value.add_theme_font_override("font", fr) + label_mp_value.add_theme_font_size_override("font_size", 10) + label_mp_value.text = "0/0" + vbox_mp.add_child(label_mp_value) + hbox.add_child(vbox_mp) + hbox.move_child(vbox_mp, 1) + func _update_player_health(): if not local_player or not texture_progress_bar_hp: return @@ -238,6 +298,35 @@ func _update_player_health(): # Update progress bar texture_progress_bar_hp.max_value = max_health texture_progress_bar_hp.value = health + + # HP value label (like inventory) + if label_hp_value: + label_hp_value.text = str(int(health)) + "/" + str(int(max_health)) + + # Race label: "- DWARF -" / "- ELF -" / "- WIZARD -" (Human -> Wizard) + if label_life and local_player.character_stats: + var r = local_player.character_stats.race + var t = "- " + if r == "Dwarf": + t += "DWARF" + elif r == "Elf": + t += "ELF" + elif r == "Human": + t += "WIZARD" + else: + t += "LIFE" + label_life.text = t + " -" + +func _update_player_mp() -> void: + if not local_player or not progress_bar_mp or not label_mp_value: + return + if not local_player.character_stats: + return + var mp = local_player.character_stats.mp + var max_mp = local_player.character_stats.maxmp + progress_bar_mp.max_value = max(1.0, max_mp) + progress_bar_mp.value = mp + label_mp_value.text = str(int(mp)) + "/" + str(int(max_mp)) func _update_keys_display(): if not local_player or not label_keys_value: diff --git a/src/scripts/interactable_object.gd b/src/scripts/interactable_object.gd index e9cf39e..e60deef 100644 --- a/src/scripts/interactable_object.gd +++ b/src/scripts/interactable_object.gd @@ -838,7 +838,11 @@ func _open_chest(by_player: Node = null): if by_player and is_instance_valid(by_player) and by_player.is_in_group("player") and chest_item: # Add item to player inventory if by_player.character_stats: + var was_encumbered = by_player.character_stats.is_over_encumbered() by_player.character_stats.add_item(chest_item) + if not was_encumbered and by_player.character_stats.is_over_encumbered(): + if by_player.has_method("show_floating_status"): + by_player.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2)) # Show pickup notification var items_texture = load(chest_item.spritePath) if chest_item.spritePath != "" else null @@ -853,12 +857,11 @@ func _open_chest(by_player: Node = null): else: item_color = Color.WHITE - # Show notification with item sprite + # Show notification with item sprite (pass chest_item for ItemSprite colorization) if items_texture: - _show_item_pickup_notification(by_player, display_text, item_color, items_texture, chest_item.spriteFrames.x, chest_item.spriteFrames.y, chest_item.spriteFrame) + _show_item_pickup_notification(by_player, display_text, item_color, items_texture, chest_item.spriteFrames.x, chest_item.spriteFrames.y, chest_item.spriteFrame, chest_item) else: - # Fallback: just show text - _show_item_pickup_notification(by_player, display_text, item_color, null, 0, 0, 0) + _show_item_pickup_notification(by_player, display_text, item_color, null, 0, 0, 0, chest_item) # Play chest open sound if has_node("SfxChestOpen"): @@ -955,9 +958,9 @@ func _sync_chest_open(loot_type_str: String = "coin", player_peer_id: int = 0, i item_color = Color.CYAN # Cyan for equipment (matches loot pickup color) if items_texture: - _show_item_pickup_notification(player, display_text, item_color, items_texture, chest_item.spriteFrames.x, chest_item.spriteFrames.y, chest_item.spriteFrame) + _show_item_pickup_notification(player, display_text, item_color, items_texture, chest_item.spriteFrames.x, chest_item.spriteFrames.y, chest_item.spriteFrame, chest_item) else: - _show_item_pickup_notification(player, display_text, item_color, null, 0, 0, 0) + _show_item_pickup_notification(player, display_text, item_color, null, 0, 0, 0, chest_item) else: # Fallback to old loot type system (for backwards compatibility) match loot_type_str: @@ -980,7 +983,7 @@ func _sync_chest_open(loot_type_str: String = "coin", player_peer_id: int = 0, i var items_texture = load("res://assets/gfx/pickups/items_n_shit.png") _show_item_pickup_notification(player, "+1 KEY", Color.YELLOW, items_texture, 20, 14, (13 * 20) + 10) -func _show_item_pickup_notification(player: Node, text: String, text_color: Color, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0): +func _show_item_pickup_notification(player: Node, text: String, text_color: Color, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0, item = null): # Show item graphic and text above player's head for 0.5s, then fade out over 0.5s var floating_text_scene = preload("res://scenes/floating_text.tscn") if floating_text_scene and player and is_instance_valid(player): @@ -990,7 +993,7 @@ func _show_item_pickup_notification(player: Node, text: String, text_color: Colo parent.add_child(floating_text) # Position at player.position.y - 20 (just above head) floating_text.global_position = Vector2(player.global_position.x, player.global_position.y - 20) - floating_text.setup(text, text_color, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame) # Show for 0.5s, fade over 0.5s + floating_text.setup(text, text_color, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame, item) func play_destroy_sound(): if object_type == "Pot": diff --git a/src/scripts/inventory_ui.gd b/src/scripts/inventory_ui.gd index de9e363..f9ca1a8 100644 --- a/src/scripts/inventory_ui.gd +++ b/src/scripts/inventory_ui.gd @@ -78,6 +78,21 @@ var equipment_buttons: Dictionary = {} # slot_name -> button var inventory_items_list: Array = [] # Flat list of items for navigation var inventory_rows_list: Array = [] # List of HBoxContainers (rows) +# Level-up stat allocation (when pending_level_up) +var level_up_label: Label = null +var level_up_stat_buttons: Array = [] # Buttons for STR, DEX, INT, END, WIS, LCK, PER +var level_up_stat_container: HBoxContainer = null +var selected_level_up_stat_index: int = -1 +const STAT_DESCRIPTIONS: Dictionary = { + "str": "STR: Physical damage, carry capacity.", + "dex": "DEX: Dodge, hit chance, move & attack speed.", + "int": "INT: Spell damage, mana, sight.", + "end": "END: Max HP.", + "wis": "WIS: Mana regen, resistances.", + "lck": "LCK: Critical hit chance.", + "per": "PER: Trap detection, perception." +} + # Equipment slot buttons var equipment_slots: Dictionary = { "mainhand": null, @@ -116,6 +131,9 @@ func _ready(): _create_exp_ui() _create_coin_ui() + # Level-up stat allocation UI (label + stat buttons) + _setup_level_up_ui() + # Setup selection rectangle (already in scene, just configure it) _setup_selection_rectangle() @@ -208,6 +226,16 @@ func _update_stats(): var race_text = char_stats.race stats_label.text = "Stats - " + race_text + # Level-up UI: "Level X - LEVEL UP" in green, stat allocation buttons + var pending = char_stats.pending_level_up and char_stats.pending_stat_points > 0 + if level_up_label: + level_up_label.visible = pending + if pending: + level_up_label.text = "Level " + str(char_stats.level) + " - LEVEL UP" + level_up_label.add_theme_color_override("font_color", Color(0.2, 1.0, 0.4)) + if level_up_stat_container: + level_up_stat_container.visible = pending + # Base stats: Level, STR, DEX, END, INT, WIS, LCK, PER (HP/MP are bars below) if label_base_stats: label_base_stats.text = "Level\n\nSTR\nDEX\nEND\nINT\nWIS\nLCK\nPER" @@ -279,6 +307,82 @@ func _update_stats(): fill_style.bg_color = Color(0.9, 0.3, 0.2) weight_progress_bar.add_theme_stylebox_override("fill", fill_style) +func _setup_level_up_ui() -> void: + if not stats_panel: + return + level_up_label = Label.new() + level_up_label.name = "LevelUpLabel" + level_up_label.add_theme_font_size_override("font_size", 14) + level_up_label.add_theme_color_override("font_color", Color(0.2, 1.0, 0.4)) + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var fr = load("res://assets/fonts/standard_font.png") + if fr: + level_up_label.add_theme_font_override("font", fr) + level_up_label.visible = false + stats_panel.add_child(level_up_label) + stats_panel.move_child(level_up_label, 1) + level_up_stat_container = HBoxContainer.new() + level_up_stat_container.name = "LevelUpStatButtons" + level_up_stat_container.add_theme_constant_override("separation", 4) + level_up_stat_container.visible = false + for stat_name in CharacterStats.LEVEL_UP_STAT_NAMES: + var btn = Button.new() + btn.name = "LevelUp_" + stat_name + btn.text = stat_name.to_upper() + btn.custom_minimum_size = Vector2(32, 24) + btn.flat = true + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var fr = load("res://assets/fonts/standard_font.png") + if fr: + btn.add_theme_font_override("font", fr) + btn.add_theme_font_size_override("font_size", 9) + btn.focus_mode = Control.FOCUS_ALL + btn.pressed.connect(_on_level_up_stat_pressed.bind(stat_name)) + btn.mouse_entered.connect(_on_level_up_stat_hover_entered.bind(stat_name)) + btn.mouse_exited.connect(_on_level_up_stat_hover_exited) + btn.gui_input.connect(_on_level_up_stat_gui_input.bind(stat_name, btn)) + level_up_stat_container.add_child(btn) + level_up_stat_buttons.append(btn) + stats_panel.add_child(level_up_stat_container) + stats_panel.move_child(level_up_stat_container, 2) + +func _on_level_up_stat_pressed(stat_name: String) -> void: + if not local_player or not local_player.character_stats: + return + if local_player.character_stats.allocate_stat_point(stat_name): + if sfx_armour: + sfx_armour.play() + _update_stats() + if not local_player.character_stats.pending_level_up: + selected_type = "equipment" + selected_level_up_stat_index = -1 + var next_index = _find_next_filled_equipment_slot(-1, 1) + if next_index >= 0: + equipment_selection_index = next_index + selected_slot = equipment_slots_list[next_index] + selected_item = local_player.character_stats.equipment[selected_slot] + else: + selected_slot = "" + selected_item = null + if inventory_items_list.size() > 0: + selected_type = "item" + inventory_selection_row = 0 + inventory_selection_col = 0 + _update_ui() + +func _on_level_up_stat_hover_entered(stat_name: String) -> void: + if info_label and stat_name in STAT_DESCRIPTIONS: + info_label.text = STAT_DESCRIPTIONS[stat_name] + +func _on_level_up_stat_hover_exited() -> void: + if info_label: + _update_info_panel() + +func _on_level_up_stat_gui_input(event: InputEvent, stat_name: String, btn: Button) -> void: + if event is InputEventKey and event.pressed and not event.echo: + if event.keycode == KEY_ENTER or event.keycode == KEY_KP_ENTER or event.keycode == KEY_SPACE: + _on_level_up_stat_pressed(stat_name) + func _create_equipment_slots(): # Equipment slot order: mainhand, offhand, headgear, armour, boots, accessory var slot_names = ["mainhand", "offhand", "headgear", "armour", "boots", "accessory"] @@ -1062,6 +1166,17 @@ func _navigate_equipment(direction: String): var next_index = _find_next_filled_equipment_slot(equipment_selection_index, -1) if next_index >= 0: equipment_selection_index = next_index + elif local_player and local_player.character_stats and local_player.character_stats.pending_level_up and local_player.character_stats.pending_stat_points > 0 and level_up_stat_buttons.size() > 0: + selected_type = "level_up_stat" + selected_level_up_stat_index = 0 + selected_slot = "" + selected_item = null + level_up_stat_buttons[0].call_deferred("grab_focus") + if info_label and CharacterStats.LEVEL_UP_STAT_NAMES.size() > 0: + var sn = CharacterStats.LEVEL_UP_STAT_NAMES[0] + if sn in STAT_DESCRIPTIONS: + info_label.text = STAT_DESCRIPTIONS[sn] + return "right": var next_index = _find_next_filled_equipment_slot(equipment_selection_index, 1) if next_index >= 0: @@ -1125,6 +1240,44 @@ func _navigate_equipment(direction: String): _update_selection_rectangle() _update_info_panel() +func _navigate_level_up_stats(direction: String) -> void: + var n = level_up_stat_buttons.size() + if n == 0: + return + match direction: + "left": + selected_level_up_stat_index = (selected_level_up_stat_index - 1 + n) % n + level_up_stat_buttons[selected_level_up_stat_index].call_deferred("grab_focus") + if info_label and selected_level_up_stat_index < CharacterStats.LEVEL_UP_STAT_NAMES.size(): + var sn = CharacterStats.LEVEL_UP_STAT_NAMES[selected_level_up_stat_index] + if sn in STAT_DESCRIPTIONS: + info_label.text = STAT_DESCRIPTIONS[sn] + "right": + if selected_level_up_stat_index >= n - 1: + # Past last stat: go to equipment + selected_type = "equipment" + selected_level_up_stat_index = -1 + selected_slot = "" + selected_item = null + var next_index = _find_next_filled_equipment_slot(-1, 1) + if next_index >= 0: + equipment_selection_index = next_index + selected_slot = equipment_slots_list[next_index] + selected_item = local_player.character_stats.equipment[selected_slot] if local_player and local_player.character_stats else null + _update_selection_from_navigation() + _update_selection_rectangle() + _update_info_panel() + return + selected_level_up_stat_index += 1 + level_up_stat_buttons[selected_level_up_stat_index].call_deferred("grab_focus") + if info_label and selected_level_up_stat_index < CharacterStats.LEVEL_UP_STAT_NAMES.size(): + var sn = CharacterStats.LEVEL_UP_STAT_NAMES[selected_level_up_stat_index] + if sn in STAT_DESCRIPTIONS: + info_label.text = STAT_DESCRIPTIONS[sn] + "up", "down": + pass + # Don't call _update_info_panel - we've set stat description above + func _on_inventory_item_pressed(item: Item): if not local_player or not local_player.character_stats: return @@ -1221,7 +1374,9 @@ func _input(event): direction = "down" if direction != "": - if selected_type == "equipment": + if selected_type == "level_up_stat": + _navigate_level_up_stats(direction) + elif selected_type == "equipment": _navigate_equipment(direction) else: _navigate_inventory(direction) @@ -1239,6 +1394,16 @@ func _input(event): _handle_e_key() get_viewport().set_input_as_handled() return + + # Attack: Allocate level-up stat when a stat button is focused + if event.is_action_pressed("attack") and not event.is_echo(): + var fc = get_viewport().gui_get_focus_owner() + if level_up_stat_container and fc and is_instance_valid(level_up_stat_container) and fc.get_parent() == level_up_stat_container: + var idx = level_up_stat_buttons.find(fc) + if idx >= 0 and idx < CharacterStats.LEVEL_UP_STAT_NAMES.size(): + _on_level_up_stat_pressed(CharacterStats.LEVEL_UP_STAT_NAMES[idx]) + get_viewport().set_input_as_handled() + return func _handle_f_key(): if not local_player or not local_player.character_stats: @@ -1562,6 +1727,7 @@ func _open_inventory(): selected_slot = "" _update_ui() + _update_stats() if not local_player: _find_local_player() diff --git a/src/scripts/loot.gd b/src/scripts/loot.gd index f5f3736..40bc284 100644 --- a/src/scripts/loot.gd +++ b/src/scripts/loot.gd @@ -613,6 +613,7 @@ func _process_pickup_on_server(player: Node): sfx_loot_collect.play() # Handle item pickup based on type + var was_encumbered = player.character_stats.is_over_encumbered() if player.character_stats else false if item.item_type == Item.ItemType.Equippable: # Equippable item - add to inventory if player.character_stats: @@ -623,6 +624,9 @@ func _process_pickup_on_server(player: Node): if player.character_stats: player.character_stats.add_item(item) print(name, " picked up item: ", item.item_name, " (added to inventory)") + if player.character_stats and not was_encumbered and player.character_stats.is_over_encumbered(): + if player.has_method("show_floating_status"): + player.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2)) # Sync inventory+equipment to joiner (server added to their copy; joiner's client must apply) if multiplayer.has_multiplayer_peer() and multiplayer.is_server(): @@ -649,7 +653,7 @@ func _process_pickup_on_server(player: Node): elif item.item_type == Item.ItemType.Restoration: text_color = Color.GREEN # Green for consumables - _show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, item.spriteFrames.x, item.spriteFrames.y, item.spriteFrame) + _show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, item.spriteFrames.x, item.spriteFrames.y, item.spriteFrame, item) # Sync floating text to client via GameWorld to avoid loot node path RPCs if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1: @@ -812,7 +816,7 @@ func _sync_show_floating_text(loot_type_value: int, text: String, color_value: C # Show floating text on client _show_floating_text(player, text, color_value, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame_value) -func _show_floating_text(player: Node, text: String, color: Color, show_time: float = 0.5, fade_time: float = 0.5, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0): +func _show_floating_text(player: Node, text: String, color: Color, show_time: float = 0.5, fade_time: float = 0.5, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0, item = null): # Create floating text and item graphic above player's head # Shows for show_time seconds, then fades out over fade_time seconds var floating_text_scene = preload("res://scenes/floating_text.tscn") @@ -822,4 +826,4 @@ func _show_floating_text(player: Node, text: String, color: Color, show_time: fl if parent: parent.add_child(floating_text) floating_text.global_position = Vector2(player.global_position.x, player.global_position.y - 20) - floating_text.setup(text, color, show_time, fade_time, item_texture, sprite_hframes, sprite_vframes, sprite_frame) + floating_text.setup(text, color, show_time, fade_time, item_texture, sprite_hframes, sprite_vframes, sprite_frame, item) diff --git a/src/scripts/minimap.gd b/src/scripts/minimap.gd new file mode 100644 index 0000000..0c79e0c --- /dev/null +++ b/src/scripts/minimap.gd @@ -0,0 +1,82 @@ +extends Control + +# Minimap: shows explored dungeon tiles (uses same "explored" data as fog of war). +# Populates as the player explores. Drawn in upper-right corner. + +const MINIMAP_WIDTH: int = 128 +const MINIMAP_HEIGHT: int = 96 +const COLOR_UNEXPLORED: Color = Color(0.08, 0.08, 0.1) +const COLOR_WALL: Color = Color(0.22, 0.22, 0.26) +const COLOR_FLOOR: Color = Color(0.38, 0.38, 0.44) +const COLOR_PLAYER: Color = Color(1.0, 0.35, 0.35) +const COLOR_EXIT: Color = Color(1.0, 1.0, 1.0) + +var _map_size: Vector2i = Vector2i.ZERO +var _explored_map: PackedInt32Array = PackedInt32Array() +var _grid: Array = [] # 2D grid [x][y]: 0=wall, 1=floor, 2=door, 3=corridor +var _player_tile: Vector2i = Vector2i(-1, -1) +var _exit_tile: Vector2i = Vector2i(-1, -1) +var _exit_discovered: bool = false + + +func set_maps(explored_map: PackedInt32Array, map_size: Vector2i, grid: Array, player_tile: Vector2i = Vector2i(-1, -1), exit_tile: Vector2i = Vector2i(-1, -1), exit_discovered: bool = false) -> void: + _explored_map = explored_map + _map_size = map_size + _grid = grid + _player_tile = player_tile + _exit_tile = exit_tile + _exit_discovered = exit_discovered + queue_redraw() + + +func _ready() -> void: + custom_minimum_size = Vector2(MINIMAP_WIDTH, MINIMAP_HEIGHT) + mouse_filter = Control.MOUSE_FILTER_IGNORE + + +func _process(_delta: float) -> void: + if _exit_discovered: + queue_redraw() + + +func _draw() -> void: + if _map_size.x <= 0 or _map_size.y <= 0 or _explored_map.is_empty(): + return + var bw := float(MINIMAP_WIDTH) + var bh := float(MINIMAP_HEIGHT) + var tw := bw / float(_map_size.x) + var th := bh / float(_map_size.y) + for x in range(_map_size.x): + for y in range(_map_size.y): + var idx := x + y * _map_size.x + if idx < 0 or idx >= _explored_map.size(): + continue + var explored := _explored_map[idx] != 0 + var px := float(x) * tw + var py := float(y) * th + var rect := Rect2(px, py, tw, th) + var col: Color + if not explored: + col = COLOR_UNEXPLORED + else: + var g: int = 0 + if _grid.size() > x and _grid[x] is Array and (_grid[x] as Array).size() > y: + var row = _grid[x] as Array + g = int(row[y]) + if g == 0: + col = COLOR_WALL + else: + col = COLOR_FLOOR + draw_rect(rect, col, true) + if _player_tile.x >= 0 and _player_tile.y >= 0 and _player_tile.x < _map_size.x and _player_tile.y < _map_size.y: + var px := float(_player_tile.x) * tw + tw * 0.5 + var py := float(_player_tile.y) * th + th * 0.5 + var r := maxf(2.0, minf(tw, th) * 0.4) + draw_circle(Vector2(px, py), r, COLOR_PLAYER) + if _exit_discovered and _exit_tile.x >= 0 and _exit_tile.y >= 0 and _exit_tile.x < _map_size.x and _exit_tile.y < _map_size.y: + var ex := float(_exit_tile.x) * tw + tw * 0.5 + var ey := float(_exit_tile.y) * th + th * 0.5 + var er := maxf(2.0, minf(tw, th) * 0.35) + var blink := sin(Time.get_ticks_msec() * 0.004) * 0.5 + 0.5 + var exit_col := Color(COLOR_EXIT.r, COLOR_EXIT.g, COLOR_EXIT.b, 0.45 + 0.55 * blink) + draw_circle(Vector2(ex, ey), er, exit_col) diff --git a/src/scripts/minimap.gd.uid b/src/scripts/minimap.gd.uid new file mode 100644 index 0000000..3a84dbe --- /dev/null +++ b/src/scripts/minimap.gd.uid @@ -0,0 +1 @@ +uid://bd4vtqviya6bd diff --git a/src/scripts/player.gd b/src/scripts/player.gd index 21c708c..17e5b24 100644 --- a/src/scripts/player.gd +++ b/src/scripts/player.gd @@ -59,6 +59,7 @@ var controls_disabled: bool = false # True when player has reached exit and cont # Being held state var being_held_by: Node = null +var grabbed_by_enemy_hand: Node = null # Set when enemy hand grabs (snatch); locks movement until release var struggle_time: float = 0.0 var struggle_threshold: float = 0.8 # Seconds to break free var struggle_direction: Vector2 = Vector2.ZERO @@ -97,7 +98,8 @@ var burn_debuff_duration: float = 5.0 # Burn lasts 5 seconds var burn_debuff_damage_per_second: float = 1.0 # 1 HP per second var burn_debuff_visual: Node2D = null # Visual indicator for burn debuff var burn_damage_timer: float = 0.0 # Timer for burn damage ticks -var movement_lock_timer: float = 0.0 # Lock movement when bow is released +var movement_lock_timer: float = 0.0 # Lock movement when bow is released or after casting spell +const SPELL_CAST_LOCK_DURATION: float = 0.28 # Lock in place briefly after casting a spell var direction_lock_timer: float = 0.0 # Lock facing direction when attacking var locked_facing_direction: Vector2 = Vector2.ZERO # Locked facing direction during attack var damage_direction_lock_timer: float = 0.0 # Lock facing when taking damage (enemies/players) @@ -124,9 +126,22 @@ var velocity_z: float = 0.0 var gravity_z: float = 500.0 # Gravity pulling down (scaled for 1x scale) var is_airborne: bool = false +# Spawn fall-down: hidden at start, fall from high Z, land with DIE+concussion, then stand up +const SPAWN_FALL_INITIAL_Z: float = 700.0 # High enough to start off-screen (y_offset = -350) +const SPAWN_FALL_GRAVITY: float = 180.0 # Slower fall so descent is visible (~2.8s from 700) +const SPAWN_LANDING_BOUNCE_GRAVITY: float = 420.0 # Heavier gravity during bounce so it's snappier +const SPAWN_LANDING_BOUNCE_UP: float = 95.0 # Upward velocity on first impact for a noticeable bounce +const SPAWN_LANDING_LAND_DURATION: float = 0.85 # Time in LAND (and concussion) before switching to STAND +const SPAWN_LANDING_STAND_DURATION: float = 0.16 # STAND anim duration (40+40+40+40 ms) before allow control +var spawn_landing: bool = false +var spawn_landing_landed: bool = false +var spawn_landing_bounced: bool = false + # Components # @onready var sprite = $Sprite2D # REMOVED: Now using layered sprites @onready var shadow = $Shadow +@onready var cone_light = $ConeLight +@onready var point_light = $PointLight2D @onready var collision_shape = $CollisionShape2D @onready var grab_area = $GrabArea @onready var interaction_indicator = $InteractionIndicator @@ -150,7 +165,6 @@ var is_airborne: bool = false @onready var sprite_shield = $Sprite2DShield @onready var sprite_shield_holding = $Sprite2DShieldHolding @onready var sprite_weapon = $Sprite2DWeapon -@onready var cone_light = $ConeLight # Player stats (legacy - now using character_stats) var max_health: float: @@ -308,6 +322,24 @@ const ANIMATIONS = { "frameDurations": [260, 260, 260, 260], "loop": true, "nextAnimation": null + }, + "FALL": { + "frames": [19, 21], + "frameDurations": [10, 10], + "loop": true, + "nextAnimation": null + }, + "LAND": { + "frames": [23], + "frameDurations": [10], + "loop": true, + "nextAnimation": null + }, + "STAND": { + "frames": [23,24,22,1], + "frameDurations": [40,40,40,40], + "loop": false, + "nextAnimation": "IDLE" } } @@ -321,6 +353,20 @@ func _ready(): # Add to player group for easy identification add_to_group("player") + # Spawn fall-down: hidden at start for everyone, then show and fall from high Z + visible = false + spawn_landing = true + spawn_landing_landed = false + spawn_landing_bounced = false + position_z = SPAWN_FALL_INITIAL_Z + velocity_z = 0.0 + is_airborne = true + if cone_light: + cone_light.visible = false + if point_light: + point_light.visible = false + call_deferred("_spawn_landing_show") + # Set respawn point to starting position respawn_point = global_position @@ -469,6 +515,11 @@ func _ready(): # Emit character_changed to trigger appearance sync for any newly connected clients character_stats.character_changed.emit(character_stats) +func _spawn_landing_show(): + if not is_instance_valid(self): + return + visible = true + func _duplicate_sprite_materials(): # Duplicate shader materials for ALL sprites that use tint parameters # This prevents shared material state between players @@ -1530,6 +1581,9 @@ func _update_facing_from_mouse(mouse_direction: Vector2): # Don't update facing when dead if is_dead: return + # Don't update facing during spawn fall/land/stand (locked to DOWN until stand up) + if spawn_landing: + return # Only update if using keyboard input (not gamepad) if input_device != -1: @@ -1702,8 +1756,11 @@ func _snap_to_8_directions(direction: Vector2) -> Vector2: return best_dir func _update_z_physics(delta): - # Apply gravity - velocity_z -= gravity_z * delta + # Apply gravity (slower spawn-fall; snappier bounce) + var g = gravity_z + if spawn_landing and not spawn_landing_landed: + g = SPAWN_LANDING_BOUNCE_GRAVITY if spawn_landing_bounced else SPAWN_FALL_GRAVITY + velocity_z -= g * delta # Update Z position position_z += velocity_z * delta @@ -1711,15 +1768,28 @@ func _update_z_physics(delta): # Check if landed if position_z <= 0.0: position_z = 0.0 - velocity_z = 0.0 - is_airborne = false - - # Stop horizontal movement on landing (with some friction) - velocity = velocity * 0.3 - - # Visual effect when landing (removed - using layered sprites now) - - print(name, " landed!") + # Spawn landing: first impact -> bounce + LAND; second impact -> settle, then STAND + if spawn_landing and not spawn_landing_landed and is_multiplayer_authority() and not spawn_landing_bounced: + spawn_landing_bounced = true + velocity_z = SPAWN_LANDING_BOUNCE_UP + velocity = velocity * 0.3 + current_direction = Direction.RIGHT + facing_direction_vector = Vector2.RIGHT + _set_animation("LAND") + if has_node("SfxFallDownLand"): + $SfxFallDownLand.play() + if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): + _rpc_to_ready_peers("_sync_spawn_bounced", [name]) + # keep is_airborne true, continue falling after bounce + else: + velocity_z = 0.0 + is_airborne = false + velocity = velocity * 0.3 + if not spawn_landing: + print(name, " landed!") + elif spawn_landing and not spawn_landing_landed and is_multiplayer_authority(): + spawn_landing_landed = true + _spawn_landing_on_land() # Update visual offset based on height for all sprite layers var y_offset = - position_z * 0.5 # Visual height is half of z for perspective @@ -1754,6 +1824,19 @@ func _physics_process(delta): # Reset teleport flag at start of frame teleported_this_frame = false + # Spawn fall: DOWN during fall; RIGHT on landing (bounce + rest). FALL until first impact, then LAND. + if spawn_landing: + if not spawn_landing_bounced: + current_direction = Direction.DOWN + facing_direction_vector = Vector2.DOWN + else: + current_direction = Direction.RIGHT + facing_direction_vector = Vector2.RIGHT + if is_airborne and not spawn_landing_landed and not spawn_landing_bounced: + _set_animation("FALL") + elif is_airborne and spawn_landing_bounced: + _set_animation("LAND") + # Update animations _update_animation(delta) @@ -1898,15 +1981,15 @@ func _physics_process(delta): burn_damage_timer = 0.0 _remove_burn_debuff() - # Skip input if controls are disabled (e.g., when inventory is open) + # Skip input if controls are disabled (e.g., when inventory is open) or spawn landing (fall → DIE → stand up) # But still allow knockback to continue (handled above) - var skip_input = controls_disabled - if controls_disabled: + var skip_input = controls_disabled or spawn_landing + if controls_disabled or spawn_landing: if not is_knocked_back: # Immediately stop movement when controls are disabled (e.g., inventory opened) velocity = Vector2.ZERO - # Reset animation to IDLE if not in a special state - if current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF": + # Reset animation to IDLE if not in a special state (skip when spawn_landing: we use DIE until stand up) + if not spawn_landing and current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF": if is_lifting: _set_animation("IDLE_HOLD") elif is_pushing: @@ -1914,7 +1997,7 @@ func _physics_process(delta): else: _set_animation("IDLE") - # Check if being held by someone + # Check if being held by someone (another player) or grabbed by enemy hand var being_held_by_someone = false for other_player in get_tree().get_nodes_in_group("player"): if other_player != self and other_player.held_object == self: @@ -1928,6 +2011,11 @@ func _physics_process(delta): _update_shield_visibility() # Handle struggle mechanic _handle_struggle(delta) + elif grabbed_by_enemy_hand: + velocity = Vector2.ZERO + is_shielding = false + was_shielding_last_frame = false + _update_shield_visibility() elif is_knocked_back: is_shielding = false was_shielding_last_frame = false @@ -2378,6 +2466,8 @@ func _handle_interactions(): heal_target = _get_heal_target() var has_valid_target = ((is_fire or is_frost) and target_pos != Vector2.ZERO) or (is_heal and heal_target != null) + # Healing: allow charge even without target (don't disable charge when hovering enemy/wall/etc.) + var can_start_charge = is_heal or has_valid_target # Check if there's a grabbable object nearby - prioritize grabbing over spell casting var nearby_grabbable = null @@ -2399,7 +2489,7 @@ func _handle_interactions(): nearby_grabbable = body break - if grab_just_pressed and not is_charging_spell and has_valid_target and not nearby_grabbable and not is_lifting and not held_object: + if grab_just_pressed and not is_charging_spell and can_start_charge and not nearby_grabbable and not is_lifting and not held_object: is_charging_spell = true current_spell_element = "healing" if is_heal else ("frost" if is_frost else "fire") spell_charge_start_time = Time.get_ticks_msec() / 1000.0 @@ -2448,6 +2538,7 @@ func _handle_interactions(): else: _cast_heal_spell(heal_target) _set_animation("FINISH_SPELL") + movement_lock_timer = SPELL_CAST_LOCK_DURATION is_charging_spell = false current_spell_element = "fire" spell_incantation_played = false @@ -2474,7 +2565,8 @@ func _handle_interactions(): print(name, " released spell (", "healing" if is_heal else ("frost" if is_frost else "fire"), ", fully: ", is_fully_charged, ")") just_grabbed_this_frame = false return - elif is_charging_spell and (not has_valid_target or is_lifting or held_object): + elif is_charging_spell and (is_lifting or held_object or ((not is_heal) and not has_valid_target)): + # Don't cancel heal charge for no target (allow hover over enemy/wall); cancel fire/frost is_charging_spell = false current_spell_element = "fire" spell_incantation_played = false @@ -2488,7 +2580,7 @@ func _handle_interactions(): $SfxSpellIncantation.stop() if multiplayer.has_multiplayer_peer(): _sync_spell_charge_end.rpc() - print(name, " spell charge cancelled (no target)") + print(name, " spell charge cancelled (no target / lift / held)") # Check for trap disarm (Dwarf only) if character_stats and character_stats.race == "Dwarf": @@ -4068,14 +4160,31 @@ func _cast_heal_spell(target: Node): if is_crit: amount = floor(amount * 2.0) - var is_revive = "is_dead" in target and target.is_dead var display_amount = int(amount) + + # Undead enemies take damage from healing spell + if target.is_in_group("enemy") and "is_undead" in target and target.is_undead: + var damage_amount = float(display_amount) + var eid = target.get_multiplayer_authority() + var my_id = multiplayer.get_unique_id() + if eid == my_id: + target.take_damage(damage_amount, global_position, is_crit) + elif multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): + target.rpc_take_damage.rpc_id(eid, damage_amount, global_position, is_crit, false, false) + else: + target.rpc_take_damage.rpc(damage_amount, global_position, is_crit, false, false) + _spawn_heal_effect_and_text(target, display_amount, is_crit, false, true) + if gw and gw.has_method("_apply_heal_spell_sync"): + _rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, damage_amount, display_amount, is_crit, false, false, false, true]) + print(name, " cast heal on undead ", target.name, " for ", display_amount, " damage (crit: ", is_crit, ")") + return + + var is_revive = "is_dead" in target and target.is_dead var actual_heal = amount var allow_overheal = false var is_overheal = false if is_revive: - # Tome revive: no overheal, full amount revives actual_heal = amount else: var overheal_chance_pct = 1.0 + lck_val * 0.3 @@ -4099,12 +4208,12 @@ func _cast_heal_spell(target: Node): else: if me == tid and actual_heal > 0: target.heal(actual_heal, allow_overheal) - _spawn_heal_effect_and_text(target, display_amount, is_crit, is_overheal) + _spawn_heal_effect_and_text(target, display_amount, is_crit, is_overheal, false) if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree() and gw and gw.has_method("_apply_heal_spell_sync"): - _rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, actual_heal, display_amount, is_crit, is_overheal, allow_overheal, is_revive]) + _rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, actual_heal, display_amount, is_crit, is_overheal, allow_overheal, is_revive, false]) print(name, " cast heal on ", target.name, " for ", display_amount, " HP", " (actual: ", actual_heal, ", crit: ", is_crit, ", overheal: ", is_overheal, ", revive: ", is_revive, ")") -func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: bool, is_overheal: bool): +func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: bool, is_overheal: bool, is_damage_to_enemy: bool = false): if not target or not is_instance_valid(target): return var game_world = get_tree().get_first_node_in_group("game_world") @@ -4118,6 +4227,9 @@ func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: boo eff.global_position = target.global_position if eff.has_method("setup"): eff.setup(target) + if is_damage_to_enemy: + # Undead: enemy's take_damage already shows damage number; we only spawn effect + return var prefix = "" if is_crit and is_overheal: prefix = "CRIT OVERHEAL! " @@ -4134,12 +4246,12 @@ func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: boo ft.setup(heal_text, Color.GREEN, 0.5, 0.5, null, 1, 1, 0) @rpc("any_peer", "reliable") -func _sync_heal_spell_via_gw(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false): +func _sync_heal_spell_via_gw(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false, is_damage_to_enemy: bool = false): if is_multiplayer_authority(): return var gw = get_tree().get_first_node_in_group("game_world") if gw and gw.has_method("_apply_heal_spell_sync"): - gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal, is_revive) + gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal, is_revive, is_damage_to_enemy) func _is_healing_spell() -> bool: if not character_stats or not character_stats.equipment.has("offhand"): @@ -4169,6 +4281,15 @@ func _get_heal_target() -> Node: if d < best_d: best_d = d best = p + for e in get_tree().get_nodes_in_group("enemy"): + if not is_instance_valid(e) or ("is_dead" in e and e.is_dead): + continue + if not ("is_undead" in e and e.is_undead): + continue + var d = e.global_position.distance_to(mouse_world) + if d < best_d: + best_d = d + best = e return best func _can_cast_spell_at(target_position: Vector2) -> bool: @@ -5580,6 +5701,34 @@ func set_being_held(held: bool): struggle_direction = Vector2.ZERO being_held_by = null +@rpc("any_peer", "reliable") +func rpc_grabbed_by_enemy_hand(enemy_name: String) -> void: + var en: Node = null + var gw = get_tree().get_first_node_in_group("game_world") + if gw: + var entities = gw.get_node_or_null("Entities") + if entities: + en = entities.get_node_or_null(enemy_name) + if not en: + en = _find_node_by_name(gw, enemy_name) + grabbed_by_enemy_hand = en if en and is_instance_valid(en) else null + velocity = Vector2.ZERO + +@rpc("any_peer", "reliable") +func rpc_released_from_enemy_hand() -> void: + grabbed_by_enemy_hand = null + +func _find_node_by_name(node: Node, n: String) -> Node: + if not node: + return null + if node.name == n: + return node + for c in node.get_children(): + var found = _find_node_by_name(c, n) + if found: + return found + return null + # RPC function called by attacker to deal damage to this player @rpc("any_peer", "reliable") func rpc_take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool = false, apply_burn_debuff: bool = false): @@ -5903,6 +6052,64 @@ func _are_all_players_dead() -> bool: return false return true +func _spawn_landing_on_land(): + if has_node("SfxFallDownLand"): + $SfxFallDownLand.play() + _set_animation("LAND") + var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus") + if status_anim and status_anim.has_animation("concussion"): + status_anim.play("concussion") + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("add_screenshake"): + gw.add_screenshake(7.0, 0.28) + if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): + _rpc_to_ready_peers("_sync_spawn_landed", [name]) + get_tree().create_timer(SPAWN_LANDING_LAND_DURATION).timeout.connect(_spawn_landing_to_stand) + +@rpc("any_peer", "reliable") +func _sync_spawn_bounced(_player_name: String): + spawn_landing_bounced = true + current_direction = Direction.RIGHT + facing_direction_vector = Vector2.RIGHT + _set_animation("LAND") + if has_node("SfxFallDownLand"): + $SfxFallDownLand.play() + +@rpc("any_peer", "reliable") +func _sync_spawn_landed(_player_name: String): + # Received on remote copies when authority lands from spawn fall + spawn_landing_landed = true + position_z = 0.0 + velocity_z = 0.0 + is_airborne = false + if has_node("SfxFallDownLand"): + $SfxFallDownLand.play() + _set_animation("LAND") + var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus") + if status_anim and status_anim.has_animation("concussion"): + status_anim.play("concussion") + get_tree().create_timer(SPAWN_LANDING_LAND_DURATION).timeout.connect(_spawn_landing_to_stand) + +func _spawn_landing_to_stand(): + if not is_instance_valid(self): + return + _set_animation("STAND") + get_tree().create_timer(SPAWN_LANDING_STAND_DURATION).timeout.connect(_spawn_landing_stand_up) + +func _spawn_landing_stand_up(): + if not is_instance_valid(self): + return + # Clear concussion status (was showing during LAND) + var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus") + if status_anim and status_anim.has_animation("idle"): + status_anim.play("idle") + # STAND's nextAnimation -> IDLE, so we're already IDLE or about to be + spawn_landing = false + if cone_light: + cone_light.visible = true + if point_light: + point_light.visible = true + func _respawn(): print(name, " respawning!") was_revived = false @@ -6542,6 +6749,24 @@ func _show_revive_cost_number(amount: int): get_tree().current_scene.add_child(damage_label) damage_label.global_position = global_position + Vector2(0, -16) +func show_floating_status(text: String, col: Color = Color.WHITE) -> void: + """Show a damage-number-style floating text above player (e.g. 'Encumbered!').""" + var damage_number_scene = preload("res://scenes/damage_number.tscn") + if not damage_number_scene: + return + var lbl = damage_number_scene.instantiate() + if not lbl: + return + lbl.label = text + lbl.color = col + lbl.z_index = 5 + lbl.direction = Vector2(0, -1) + var game_world = get_tree().get_first_node_in_group("game_world") + var parent = game_world.get_node_or_null("Entities") if game_world else get_tree().current_scene + if parent: + parent.add_child(lbl) + lbl.global_position = global_position + Vector2(0, -20) + @rpc("any_peer", "reliable") func _sync_revive_cost(amount: int): if is_multiplayer_authority(): @@ -6589,12 +6814,16 @@ func _on_level_up_stats(stats_increased: Array): var base_y_offset = -32.0 # Start above player head var y_spacing = 12.0 # Space between each text - # Show "LEVEL UP!" first (in white) + # Show "LEVEL UP +1!" prominently (gold, larger, longer on screen) var level_up_text = damage_number_scene.instantiate() if level_up_text: - level_up_text.label = "LEVEL UP!" - level_up_text.color = Color.WHITE - level_up_text.direction = Vector2(0, -1) # Straight up + level_up_text.label = "LEVEL UP +1!" + level_up_text.color = Color(1.0, 0.88, 0.2) # Gold + level_up_text.direction = Vector2(0, -1) + level_up_text.rise_distance = 48.0 + level_up_text.fade_delay = 1.4 + level_up_text.fade_duration = 0.6 + level_up_text.add_theme_font_size_override("font_size", 20) entities_node.add_child(level_up_text) level_up_text.global_position = global_position + Vector2(0, base_y_offset) base_y_offset -= y_spacing